mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 09:01:49 +00:00 
			
		
		
		
	Compare commits
	
		
			116 Commits
		
	
	
		
			jesserockz
			...
			jesserockz
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					dfb98b523f | ||
| 
						 | 
					17204baac0 | ||
| 
						 | 
					1e05bcaa61 | ||
| 
						 | 
					18690d51f5 | ||
| 
						 | 
					2aacf14e96 | ||
| 
						 | 
					9c5507ab46 | ||
| 
						 | 
					0a9703bff9 | ||
| 
						 | 
					67bd5db6d6 | ||
| 
						 | 
					6c11f0bd51 | ||
| 
						 | 
					e7556271e7 | ||
| 
						 | 
					8045b889d3 | ||
| 
						 | 
					6f074d3692 | ||
| 
						 | 
					b09781afa5 | ||
| 
						 | 
					1863523cfd | ||
| 
						 | 
					a7a9eb6f71 | ||
| 
						 | 
					c868dae44a | ||
| 
						 | 
					ad8cf69897 | ||
| 
						 | 
					96f1a146a6 | ||
| 
						 | 
					775e03cfd9 | ||
| 
						 | 
					80e5e19956 | ||
| 
						 | 
					8f16268572 | ||
| 
						 | 
					0fe18a6144 | ||
| 
						 | 
					a6d1aa91de | ||
| 
						 | 
					ba11f2ab0c | ||
| 
						 | 
					9747811b82 | ||
| 
						 | 
					ff803aa108 | ||
| 
						 | 
					8bac82f804 | ||
| 
						 | 
					6682451ee0 | ||
| 
						 | 
					c17090c1e5 | ||
| 
						 | 
					acf69bb56f | ||
| 
						 | 
					fd7a212562 | ||
| 
						 | 
					8567877f07 | ||
| 
						 | 
					310f850ee4 | ||
| 
						 | 
					896cdab22d | ||
| 
						 | 
					ed6462fa00 | ||
| 
						 | 
					65b05af014 | ||
| 
						 | 
					c18056bdda | ||
| 
						 | 
					65a79acfb9 | ||
| 
						 | 
					18d331d284 | ||
| 
						 | 
					c053a33fe8 | ||
| 
						 | 
					ff07637dfd | ||
| 
						 | 
					43b5c2deb7 | ||
| 
						 | 
					d27e7b3b70 | ||
| 
						 | 
					5dec62bf1e | ||
| 
						 | 
					7d642147c1 | ||
| 
						 | 
					4c313bc198 | ||
| 
						 | 
					a78b2d0128 | ||
| 
						 | 
					f6848fe24d | ||
| 
						 | 
					a59c9b4f77 | ||
| 
						 | 
					c30913ccde | ||
| 
						 | 
					41f810f828 | ||
| 
						 | 
					d604c8ae64 | ||
| 
						 | 
					67d8c7c691 | ||
| 
						 | 
					015cd42a2e | ||
| 
						 | 
					51c5d1714c | ||
| 
						 | 
					1ff302b341 | ||
| 
						 | 
					cfe28ce7a3 | ||
| 
						 | 
					25a3db1637 | ||
| 
						 | 
					65638bf614 | ||
| 
						 | 
					1e66241b26 | ||
| 
						 | 
					eb50f0eafd | ||
| 
						 | 
					6b89763ad6 | ||
| 
						 | 
					253303f3a9 | ||
| 
						 | 
					d49f2cbec8 | ||
| 
						 | 
					290816be11 | ||
| 
						 | 
					2fc43fa9c7 | ||
| 
						 | 
					5adadeaa07 | ||
| 
						 | 
					761aae6f89 | ||
| 
						 | 
					b29e1acab8 | ||
| 
						 | 
					49d4260cfe | ||
| 
						 | 
					4e8a7986cd | ||
| 
						 | 
					3db71b98ae | ||
| 
						 | 
					73cb3ec852 | ||
| 
						 | 
					91e72fe121 | ||
| 
						 | 
					be486e0ca6 | ||
| 
						 | 
					fdefc825bb | ||
| 
						 | 
					c4c46c206f | ||
| 
						 | 
					8453d9a70d | ||
| 
						 | 
					68dbf35b09 | ||
| 
						 | 
					1a242f94db | ||
| 
						 | 
					df52bc3493 | ||
| 
						 | 
					2044c7e4d4 | ||
| 
						 | 
					b401b5eca8 | ||
| 
						 | 
					67f41a0c72 | ||
| 
						 | 
					8a83670f54 | ||
| 
						 | 
					bd7e8fbf86 | ||
| 
						 | 
					f9f98fa6c6 | ||
| 
						 | 
					f25c296303 | ||
| 
						 | 
					bc408ad08c | ||
| 
						 | 
					e2c1af199c | ||
| 
						 | 
					7c843437a7 | ||
| 
						 | 
					4bf7c97088 | ||
| 
						 | 
					7b9fb57bb2 | ||
| 
						 | 
					699d00e218 | ||
| 
						 | 
					e2784d077d | ||
| 
						 | 
					13fabf1cd8 | ||
| 
						 | 
					7b60543afd | ||
| 
						 | 
					562700bd2c | ||
| 
						 | 
					a64106e48c | ||
| 
						 | 
					c723fd1f80 | ||
| 
						 | 
					3a97244b83 | ||
| 
						 | 
					1f8449ec0e | ||
| 
						 | 
					3cd2fb0843 | ||
| 
						 | 
					7dc07c5632 | ||
| 
						 | 
					95e45dc12c | ||
| 
						 | 
					51a8a7e875 | ||
| 
						 | 
					dceab6ce29 | ||
| 
						 | 
					6de79d6cfb | ||
| 
						 | 
					7b45498de6 | ||
| 
						 | 
					618102fe8c | ||
| 
						 | 
					38b7bed2fa | ||
| 
						 | 
					d77ea46157 | ||
| 
						 | 
					8718e15a6a | ||
| 
						 | 
					861a23d039 | ||
| 
						 | 
					276eea2b69 | ||
| 
						 | 
					ccab57fc58 | 
							
								
								
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							@@ -46,7 +46,7 @@ runs:
 | 
			
		||||
 | 
			
		||||
    - name: Build and push to ghcr by digest
 | 
			
		||||
      id: build-ghcr
 | 
			
		||||
      uses: docker/build-push-action@v5.3.0
 | 
			
		||||
      uses: docker/build-push-action@v6.1.0
 | 
			
		||||
      with:
 | 
			
		||||
        context: .
 | 
			
		||||
        file: ./docker/Dockerfile
 | 
			
		||||
@@ -69,7 +69,7 @@ runs:
 | 
			
		||||
 | 
			
		||||
    - name: Build and push to dockerhub by digest
 | 
			
		||||
      id: build-dockerhub
 | 
			
		||||
      uses: docker/build-push-action@v5.3.0
 | 
			
		||||
      uses: docker/build-push-action@v6.1.0
 | 
			
		||||
      with:
 | 
			
		||||
        context: .
 | 
			
		||||
        file: ./docker/Dockerfile
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							@@ -21,7 +21,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v5.1.0
 | 
			
		||||
        with:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -40,7 +40,7 @@ jobs:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        build_type: ["ha-addon", "docker", "lint"]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.6
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v5.1.0
 | 
			
		||||
        with:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										47
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -34,7 +34,7 @@ jobs:
 | 
			
		||||
      cache-key: ${{ steps.cache-key.outputs.key }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Generate cache-key
 | 
			
		||||
        id: cache-key
 | 
			
		||||
        run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
 | 
			
		||||
@@ -66,7 +66,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -87,7 +87,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -108,7 +108,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -129,7 +129,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -150,7 +150,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -199,7 +199,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -229,7 +229,7 @@ jobs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -254,7 +254,7 @@ jobs:
 | 
			
		||||
      matrix: ${{ steps.set-matrix.outputs.matrix }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Find all YAML test files
 | 
			
		||||
        id: set-matrix
 | 
			
		||||
        run: echo "matrix=$(ls tests/test*.yaml | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
 | 
			
		||||
@@ -271,7 +271,7 @@ jobs:
 | 
			
		||||
        file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -303,7 +303,7 @@ jobs:
 | 
			
		||||
        file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -358,7 +358,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -387,6 +387,13 @@ jobs:
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
			
		||||
 | 
			
		||||
      - name: Run 'pio run --list-targets -e esp32-idf-tidy'
 | 
			
		||||
        if: matrix.name == 'Run script/clang-tidy for ESP32 IDF'
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          mkdir -p .temp
 | 
			
		||||
          pio run --list-targets -e esp32-idf-tidy
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-tidy
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
@@ -410,7 +417,7 @@ jobs:
 | 
			
		||||
      count: ${{ steps.list-components.outputs.count }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        with:
 | 
			
		||||
          # Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
 | 
			
		||||
          fetch-depth: 500
 | 
			
		||||
@@ -454,11 +461,11 @@ jobs:
 | 
			
		||||
      matrix:
 | 
			
		||||
        file: ${{ fromJson(needs.list-components.outputs.components) }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Install libsodium
 | 
			
		||||
        run: sudo apt-get install libsodium-dev
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: sudo apt-get install libsodium-dev libsdl2-dev
 | 
			
		||||
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
@@ -484,7 +491,7 @@ jobs:
 | 
			
		||||
      matrix: ${{ steps.split.outputs.components }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Split components into 20 groups
 | 
			
		||||
        id: split
 | 
			
		||||
        run: |
 | 
			
		||||
@@ -508,11 +515,11 @@ jobs:
 | 
			
		||||
      - name: List components
 | 
			
		||||
        run: echo ${{ matrix.components }}
 | 
			
		||||
 | 
			
		||||
      - name: Install libsodium
 | 
			
		||||
        run: sudo apt-get install libsodium-dev
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: sudo apt-get install libsodium-dev libsdl2-dev
 | 
			
		||||
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -19,7 +19,7 @@ jobs:
 | 
			
		||||
      tag: ${{ steps.tag.outputs.tag }}
 | 
			
		||||
      branch_build: ${{ steps.tag.outputs.branch_build }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.6
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Get tag
 | 
			
		||||
        id: tag
 | 
			
		||||
        # yamllint disable rule:line-length
 | 
			
		||||
@@ -51,7 +51,7 @@ jobs:
 | 
			
		||||
      contents: read
 | 
			
		||||
      id-token: write
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.6
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v5.1.0
 | 
			
		||||
        with:
 | 
			
		||||
@@ -65,7 +65,7 @@ jobs:
 | 
			
		||||
          pip3 install build
 | 
			
		||||
          python3 -m build
 | 
			
		||||
      - name: Publish
 | 
			
		||||
        uses: pypa/gh-action-pypi-publish@v1.8.14
 | 
			
		||||
        uses: pypa/gh-action-pypi-publish@v1.9.0
 | 
			
		||||
 | 
			
		||||
  deploy-docker:
 | 
			
		||||
    name: Build ESPHome ${{ matrix.platform }}
 | 
			
		||||
@@ -83,7 +83,7 @@ jobs:
 | 
			
		||||
          - linux/arm/v7
 | 
			
		||||
          - linux/arm64
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.6
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v5.1.0
 | 
			
		||||
        with:
 | 
			
		||||
@@ -174,7 +174,7 @@ jobs:
 | 
			
		||||
          - ghcr
 | 
			
		||||
          - dockerhub
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.6
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
 | 
			
		||||
      - name: Download digests
 | 
			
		||||
        uses: actions/download-artifact@v4.1.7
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							@@ -13,10 +13,10 @@ jobs:
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
 | 
			
		||||
      - name: Checkout Home Assistant
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        with:
 | 
			
		||||
          repository: home-assistant/core
 | 
			
		||||
          path: lib/home-assistant
 | 
			
		||||
@@ -36,7 +36,7 @@ jobs:
 | 
			
		||||
          python ./script/sync-device_class.py
 | 
			
		||||
 | 
			
		||||
      - name: Commit changes
 | 
			
		||||
        uses: peter-evans/create-pull-request@v6.0.5
 | 
			
		||||
        uses: peter-evans/create-pull-request@v6.1.0
 | 
			
		||||
        with:
 | 
			
		||||
          commit-message: "Synchronise Device Classes from Home Assistant"
 | 
			
		||||
          committer: esphomebot <esphome@nabucasa.com>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/yaml-lint.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/yaml-lint.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,7 +18,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Run yamllint
 | 
			
		||||
        uses: frenck/action-yamllint@v1.5.0
 | 
			
		||||
        with:
 | 
			
		||||
 
 | 
			
		||||
@@ -94,6 +94,7 @@ esphome/components/current_based/* @djwmarcx
 | 
			
		||||
esphome/components/dac7678/* @NickB1
 | 
			
		||||
esphome/components/daikin_arc/* @MagicBear
 | 
			
		||||
esphome/components/daikin_brc/* @hagak
 | 
			
		||||
esphome/components/dallas_temp/* @ssieb
 | 
			
		||||
esphome/components/daly_bms/* @s1lvi0
 | 
			
		||||
esphome/components/dashboard_import/* @esphome/core
 | 
			
		||||
esphome/components/datetime/* @jesserockz @rfdarter
 | 
			
		||||
@@ -144,6 +145,7 @@ esphome/components/gdk101/* @Szewcson
 | 
			
		||||
esphome/components/globals/* @esphome/core
 | 
			
		||||
esphome/components/gp8403/* @jesserockz
 | 
			
		||||
esphome/components/gpio/* @esphome/core
 | 
			
		||||
esphome/components/gpio/one_wire/* @ssieb
 | 
			
		||||
esphome/components/gps/* @coogle
 | 
			
		||||
esphome/components/graph/* @synco
 | 
			
		||||
esphome/components/graphical_display_menu/* @MrMDavidson
 | 
			
		||||
@@ -172,6 +174,7 @@ esphome/components/host/time/* @clydebarrow
 | 
			
		||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
 | 
			
		||||
esphome/components/hte501/* @Stock-M
 | 
			
		||||
esphome/components/http_request/ota/* @oarcher
 | 
			
		||||
esphome/components/http_request/update/* @jesserockz
 | 
			
		||||
esphome/components/htu31d/* @betterengineering
 | 
			
		||||
esphome/components/hydreon_rgxx/* @functionpointer
 | 
			
		||||
esphome/components/hyt271/* @Philippe12
 | 
			
		||||
@@ -269,6 +272,7 @@ esphome/components/nextion/text_sensor/* @senexcrenshaw
 | 
			
		||||
esphome/components/nfc/* @jesserockz @kbx81
 | 
			
		||||
esphome/components/noblex/* @AGalfra
 | 
			
		||||
esphome/components/number/* @esphome/core
 | 
			
		||||
esphome/components/one_wire/* @ssieb
 | 
			
		||||
esphome/components/ota/* @esphome/core
 | 
			
		||||
esphome/components/output/* @esphome/core
 | 
			
		||||
esphome/components/pca6416a/* @Mat931
 | 
			
		||||
@@ -316,6 +320,7 @@ esphome/components/rtttl/* @glmnet
 | 
			
		||||
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
 | 
			
		||||
esphome/components/scd4x/* @martgras @sjtrny
 | 
			
		||||
esphome/components/script/* @esphome/core
 | 
			
		||||
esphome/components/sdl/* @clydebarrow
 | 
			
		||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
 | 
			
		||||
esphome/components/sdp3x/* @Azimath
 | 
			
		||||
esphome/components/seeed_mr24hpc1/* @limengdu
 | 
			
		||||
@@ -410,6 +415,7 @@ esphome/components/uart/button/* @ssieb
 | 
			
		||||
esphome/components/ufire_ec/* @pvizeli
 | 
			
		||||
esphome/components/ufire_ise/* @pvizeli
 | 
			
		||||
esphome/components/ultrasonic/* @OttoWinter
 | 
			
		||||
esphome/components/update/* @jesserockz
 | 
			
		||||
esphome/components/uponor_smatrix/* @kroimon
 | 
			
		||||
esphome/components/valve/* @esphome/core
 | 
			
		||||
esphome/components/vbus/* @ssieb
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,8 @@ RUN \
 | 
			
		||||
    fi; \
 | 
			
		||||
    pip3 install \
 | 
			
		||||
    --break-system-packages --no-cache-dir \
 | 
			
		||||
    platformio==6.1.13 \
 | 
			
		||||
    # Keep platformio version in sync with requirements.txt
 | 
			
		||||
    platformio==6.1.15 \
 | 
			
		||||
    # Change some platformio settings
 | 
			
		||||
    && platformio settings set enable_telemetry No \
 | 
			
		||||
    && platformio settings set check_platformio_interval 1000000 \
 | 
			
		||||
@@ -101,7 +102,7 @@ RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "a
 | 
			
		||||
    && /platformio_install_deps.py /platformio.ini --libraries
 | 
			
		||||
 | 
			
		||||
# Avoid unsafe git error when container user and file config volume permissions don't match
 | 
			
		||||
RUN git config --system --add safe.directory '/config/*'
 | 
			
		||||
RUN git config --system --add safe.directory '*'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ======================= docker-type image =======================
 | 
			
		||||
 
 | 
			
		||||
@@ -488,6 +488,15 @@ def command_run(args, config):
 | 
			
		||||
    if exit_code != 0:
 | 
			
		||||
        return exit_code
 | 
			
		||||
    _LOGGER.info("Successfully compiled program.")
 | 
			
		||||
    if CORE.is_host:
 | 
			
		||||
        from esphome.platformio_api import get_idedata
 | 
			
		||||
 | 
			
		||||
        idedata = get_idedata(config)
 | 
			
		||||
        if idedata is None:
 | 
			
		||||
            return 1
 | 
			
		||||
        program_path = idedata.raw["prog_path"]
 | 
			
		||||
        return run_external_process(program_path)
 | 
			
		||||
 | 
			
		||||
    port = choose_upload_log_host(
 | 
			
		||||
        default=args.device,
 | 
			
		||||
        check_default=None,
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,7 @@ from esphome.cpp_types import (  # noqa
 | 
			
		||||
    bool_,
 | 
			
		||||
    int_,
 | 
			
		||||
    std_ns,
 | 
			
		||||
    std_shared_ptr,
 | 
			
		||||
    std_string,
 | 
			
		||||
    std_vector,
 | 
			
		||||
    uint8,
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,13 @@ import logging
 | 
			
		||||
from esphome import automation, core
 | 
			
		||||
from esphome.components import font
 | 
			
		||||
import esphome.components.image as espImage
 | 
			
		||||
from esphome.components.image import CONF_USE_TRANSPARENCY
 | 
			
		||||
from esphome.components.image import (
 | 
			
		||||
    CONF_USE_TRANSPARENCY,
 | 
			
		||||
    LOCAL_SCHEMA,
 | 
			
		||||
    WEB_SCHEMA,
 | 
			
		||||
    SOURCE_WEB,
 | 
			
		||||
    SOURCE_LOCAL,
 | 
			
		||||
)
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
@@ -13,6 +19,9 @@ from esphome.const import (
 | 
			
		||||
    CONF_REPEAT,
 | 
			
		||||
    CONF_RESIZE,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
    CONF_SOURCE,
 | 
			
		||||
    CONF_PATH,
 | 
			
		||||
    CONF_URL,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, HexInt
 | 
			
		||||
 | 
			
		||||
@@ -43,6 +52,40 @@ SetFrameAction = animation_ns.class_(
 | 
			
		||||
    "AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
TYPED_FILE_SCHEMA = cv.typed_schema(
 | 
			
		||||
    {
 | 
			
		||||
        SOURCE_LOCAL: LOCAL_SCHEMA,
 | 
			
		||||
        SOURCE_WEB: WEB_SCHEMA,
 | 
			
		||||
    },
 | 
			
		||||
    key=CONF_SOURCE,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _file_schema(value):
 | 
			
		||||
    if isinstance(value, str):
 | 
			
		||||
        return validate_file_shorthand(value)
 | 
			
		||||
    return TYPED_FILE_SCHEMA(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FILE_SCHEMA = cv.Schema(_file_schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_file_shorthand(value):
 | 
			
		||||
    value = cv.string_strict(value)
 | 
			
		||||
    if value.startswith("http://") or value.startswith("https://"):
 | 
			
		||||
        return FILE_SCHEMA(
 | 
			
		||||
            {
 | 
			
		||||
                CONF_SOURCE: SOURCE_WEB,
 | 
			
		||||
                CONF_URL: value,
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    return FILE_SCHEMA(
 | 
			
		||||
        {
 | 
			
		||||
            CONF_SOURCE: SOURCE_LOCAL,
 | 
			
		||||
            CONF_PATH: value,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_cross_dependencies(config):
 | 
			
		||||
    """
 | 
			
		||||
@@ -67,7 +110,7 @@ ANIMATION_SCHEMA = cv.Schema(
 | 
			
		||||
    cv.All(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_ID): cv.declare_id(Animation_),
 | 
			
		||||
            cv.Required(CONF_FILE): cv.file_,
 | 
			
		||||
            cv.Required(CONF_FILE): FILE_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_RESIZE): cv.dimensions,
 | 
			
		||||
            cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
 | 
			
		||||
                espImage.IMAGE_TYPE, upper=True
 | 
			
		||||
@@ -124,7 +167,11 @@ async def animation_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    from PIL import Image
 | 
			
		||||
 | 
			
		||||
    path = CORE.relative_config_path(config[CONF_FILE])
 | 
			
		||||
    conf_file = config[CONF_FILE]
 | 
			
		||||
    if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
 | 
			
		||||
        path = CORE.relative_config_path(conf_file[CONF_PATH])
 | 
			
		||||
    elif conf_file[CONF_SOURCE] == SOURCE_WEB:
 | 
			
		||||
        path = espImage.compute_local_image_path(conf_file).as_posix()
 | 
			
		||||
    try:
 | 
			
		||||
        image = Image.open(path)
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,7 @@ service APIConnection {
 | 
			
		||||
  rpc date_command (DateCommandRequest) returns (void) {}
 | 
			
		||||
  rpc time_command (TimeCommandRequest) returns (void) {}
 | 
			
		||||
  rpc datetime_command (DateTimeCommandRequest) returns (void) {}
 | 
			
		||||
  rpc update_command (UpdateCommandRequest) returns (void) {}
 | 
			
		||||
 | 
			
		||||
  rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
 | 
			
		||||
@@ -1837,3 +1838,46 @@ message DateTimeCommandRequest {
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  fixed32 epoch_seconds = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== UPDATE ====================
 | 
			
		||||
message ListEntitiesUpdateResponse {
 | 
			
		||||
  option (id) = 116;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_UPDATE";
 | 
			
		||||
 | 
			
		||||
  string object_id = 1;
 | 
			
		||||
  fixed32 key = 2;
 | 
			
		||||
  string name = 3;
 | 
			
		||||
  string unique_id = 4;
 | 
			
		||||
 | 
			
		||||
  string icon = 5;
 | 
			
		||||
  bool disabled_by_default = 6;
 | 
			
		||||
  EntityCategory entity_category = 7;
 | 
			
		||||
  string device_class = 8;
 | 
			
		||||
}
 | 
			
		||||
message UpdateStateResponse {
 | 
			
		||||
  option (id) = 117;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_UPDATE";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  bool missing_state = 2;
 | 
			
		||||
  bool in_progress = 3;
 | 
			
		||||
  bool has_progress = 4;
 | 
			
		||||
  float progress = 5;
 | 
			
		||||
  string current_version = 6;
 | 
			
		||||
  string latest_version = 7;
 | 
			
		||||
  string title = 8;
 | 
			
		||||
  string release_summary = 9;
 | 
			
		||||
  string release_url = 10;
 | 
			
		||||
}
 | 
			
		||||
message UpdateCommandRequest {
 | 
			
		||||
  option (id) = 118;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_UPDATE";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  bool install = 2;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1287,6 +1287,51 @@ bool APIConnection::send_event_info(event::Event *event) {
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
bool APIConnection::send_update_state(update::UpdateEntity *update) {
 | 
			
		||||
  if (!this->state_subscription_)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  UpdateStateResponse resp{};
 | 
			
		||||
  resp.key = update->get_object_id_hash();
 | 
			
		||||
  resp.missing_state = !update->has_state();
 | 
			
		||||
  if (update->has_state()) {
 | 
			
		||||
    resp.in_progress = update->state == update::UpdateState::UPDATE_STATE_INSTALLING;
 | 
			
		||||
    if (update->update_info.has_progress) {
 | 
			
		||||
      resp.has_progress = true;
 | 
			
		||||
      resp.progress = update->update_info.progress;
 | 
			
		||||
    }
 | 
			
		||||
    resp.current_version = update->update_info.current_version;
 | 
			
		||||
    resp.latest_version = update->update_info.latest_version;
 | 
			
		||||
    resp.title = update->update_info.title;
 | 
			
		||||
    resp.release_summary = update->update_info.summary;
 | 
			
		||||
    resp.release_url = update->update_info.release_url;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return this->send_update_state_response(resp);
 | 
			
		||||
}
 | 
			
		||||
bool APIConnection::send_update_info(update::UpdateEntity *update) {
 | 
			
		||||
  ListEntitiesUpdateResponse msg;
 | 
			
		||||
  msg.key = update->get_object_id_hash();
 | 
			
		||||
  msg.object_id = update->get_object_id();
 | 
			
		||||
  if (update->has_own_name())
 | 
			
		||||
    msg.name = update->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("update", update);
 | 
			
		||||
  msg.icon = update->get_icon();
 | 
			
		||||
  msg.disabled_by_default = update->is_disabled_by_default();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(update->get_entity_category());
 | 
			
		||||
  msg.device_class = update->get_device_class();
 | 
			
		||||
  return this->send_list_entities_update_response(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::update_command(const UpdateCommandRequest &msg) {
 | 
			
		||||
  update::UpdateEntity *update = App.get_update_by_key(msg.key);
 | 
			
		||||
  if (update == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  update->perform();
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
 | 
			
		||||
  if (this->log_subscription_ < level)
 | 
			
		||||
    return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -164,6 +164,12 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  bool send_event_info(event::Event *event);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  bool send_update_state(update::UpdateEntity *update);
 | 
			
		||||
  bool send_update_info(update::UpdateEntity *update);
 | 
			
		||||
  void update_command(const UpdateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  void on_disconnect_response(const DisconnectResponse &value) override;
 | 
			
		||||
  void on_ping_response(const PingResponse &value) override {
 | 
			
		||||
    // we initiated ping
 | 
			
		||||
 
 | 
			
		||||
@@ -8376,6 +8376,262 @@ void DateTimeCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 6: {
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ListEntitiesUpdateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->object_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->name = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->unique_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 5: {
 | 
			
		||||
      this->icon = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
      this->device_class = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ListEntitiesUpdateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(1, this->object_id);
 | 
			
		||||
  buffer.encode_fixed32(2, this->key);
 | 
			
		||||
  buffer.encode_string(3, this->name);
 | 
			
		||||
  buffer.encode_string(4, this->unique_id);
 | 
			
		||||
  buffer.encode_string(5, this->icon);
 | 
			
		||||
  buffer.encode_bool(6, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
 | 
			
		||||
  buffer.encode_string(8, this->device_class);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesUpdateResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%" PRIu32, this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  name: ");
 | 
			
		||||
  out.append("'").append(this->name).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  unique_id: ");
 | 
			
		||||
  out.append("'").append(this->unique_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  icon: ");
 | 
			
		||||
  out.append("'").append(this->icon).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  disabled_by_default: ");
 | 
			
		||||
  out.append(YESNO(this->disabled_by_default));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  device_class: ");
 | 
			
		||||
  out.append("'").append(this->device_class).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool UpdateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->missing_state = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->in_progress = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->has_progress = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool UpdateStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 6: {
 | 
			
		||||
      this->current_version = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->latest_version = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
      this->title = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 9: {
 | 
			
		||||
      this->release_summary = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 10: {
 | 
			
		||||
      this->release_url = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool UpdateStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 5: {
 | 
			
		||||
      this->progress = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_fixed32(1, this->key);
 | 
			
		||||
  buffer.encode_bool(2, this->missing_state);
 | 
			
		||||
  buffer.encode_bool(3, this->in_progress);
 | 
			
		||||
  buffer.encode_bool(4, this->has_progress);
 | 
			
		||||
  buffer.encode_float(5, this->progress);
 | 
			
		||||
  buffer.encode_string(6, this->current_version);
 | 
			
		||||
  buffer.encode_string(7, this->latest_version);
 | 
			
		||||
  buffer.encode_string(8, this->title);
 | 
			
		||||
  buffer.encode_string(9, this->release_summary);
 | 
			
		||||
  buffer.encode_string(10, this->release_url);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void UpdateStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("UpdateStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%" PRIu32, this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  missing_state: ");
 | 
			
		||||
  out.append(YESNO(this->missing_state));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  in_progress: ");
 | 
			
		||||
  out.append(YESNO(this->in_progress));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_progress: ");
 | 
			
		||||
  out.append(YESNO(this->has_progress));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  progress: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->progress);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  current_version: ");
 | 
			
		||||
  out.append("'").append(this->current_version).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  latest_version: ");
 | 
			
		||||
  out.append("'").append(this->latest_version).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  title: ");
 | 
			
		||||
  out.append("'").append(this->title).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  release_summary: ");
 | 
			
		||||
  out.append("'").append(this->release_summary).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  release_url: ");
 | 
			
		||||
  out.append("'").append(this->release_url).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->install = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void UpdateCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_fixed32(1, this->key);
 | 
			
		||||
  buffer.encode_bool(2, this->install);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void UpdateCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("UpdateCommandRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%" PRIu32, this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  install: ");
 | 
			
		||||
  out.append(YESNO(this->install));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -2130,6 +2130,61 @@ class DateTimeCommandRequest : public ProtoMessage {
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
};
 | 
			
		||||
class ListEntitiesUpdateResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::string object_id{};
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  std::string name{};
 | 
			
		||||
  std::string unique_id{};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  std::string device_class{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class UpdateStateResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  bool missing_state{false};
 | 
			
		||||
  bool in_progress{false};
 | 
			
		||||
  bool has_progress{false};
 | 
			
		||||
  float progress{0.0f};
 | 
			
		||||
  std::string current_version{};
 | 
			
		||||
  std::string latest_version{};
 | 
			
		||||
  std::string title{};
 | 
			
		||||
  std::string release_summary{};
 | 
			
		||||
  std::string release_url{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class UpdateCommandRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  bool install{false};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -611,6 +611,24 @@ bool APIServerConnectionBase::send_date_time_state_response(const DateTimeStateR
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
bool APIServerConnectionBase::send_list_entities_update_response(const ListEntitiesUpdateResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_list_entities_update_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<ListEntitiesUpdateResponse>(msg, 116);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
bool APIServerConnectionBase::send_update_state_response(const UpdateStateResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_update_state_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<UpdateStateResponse>(msg, 117);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
#endif
 | 
			
		||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
 | 
			
		||||
  switch (msg_type) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
@@ -1106,6 +1124,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
			
		||||
      ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_voice_assistant_timer_event_response(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 118: {
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
      UpdateCommandRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_update_command_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -1434,6 +1463,19 @@ void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequ
 | 
			
		||||
  this->datetime_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->update_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
    const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
			
		||||
 
 | 
			
		||||
@@ -306,6 +306,15 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  bool send_list_entities_update_response(const ListEntitiesUpdateResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  bool send_update_state_response(const UpdateStateResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  virtual void on_update_command_request(const UpdateCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
 | 
			
		||||
@@ -373,6 +382,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  virtual void update_command(const UpdateCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -471,6 +483,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  void on_update_command_request(const UpdateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -334,6 +334,13 @@ void APIServer::on_event(event::Event *obj, const std::string &event_type) {
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
void APIServer::on_update(update::UpdateEntity *obj) {
 | 
			
		||||
  for (auto &c : this->clients_)
 | 
			
		||||
    c->send_update_state(obj);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
 | 
			
		||||
APIServer *global_api_server = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 
 | 
			
		||||
@@ -102,6 +102,9 @@ class APIServer : public Component, public Controller {
 | 
			
		||||
#ifdef USE_EVENT
 | 
			
		||||
  void on_event(event::Event *obj, const std::string &event_type) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  void on_update(update::UpdateEntity *obj) override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  bool is_connected() const;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -98,6 +98,9 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
 | 
			
		||||
#ifdef USE_EVENT
 | 
			
		||||
bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); }
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_info(update); }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -75,6 +75,9 @@ class ListEntitiesIterator : public ComponentIterator {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_EVENT
 | 
			
		||||
  bool on_event(event::Event *event) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  bool on_update(update::UpdateEntity *update) override;
 | 
			
		||||
#endif
 | 
			
		||||
  bool on_end() override;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -77,6 +77,9 @@ bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
 | 
			
		||||
  return this->client_->send_alarm_control_panel_state(a_alarm_control_panel);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
bool InitialStateIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_state(update); }
 | 
			
		||||
#endif
 | 
			
		||||
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
 
 | 
			
		||||
@@ -72,6 +72,9 @@ class InitialStateIterator : public ComponentIterator {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_EVENT
 | 
			
		||||
  bool on_event(event::Event *event) override { return true; };
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  bool on_update(update::UpdateEntity *update) override;
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  APIConnection *client_;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,18 +6,24 @@ namespace climate_ir_lg {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "climate.climate_ir_lg";
 | 
			
		||||
 | 
			
		||||
const uint32_t COMMAND_ON = 0x00000;
 | 
			
		||||
const uint32_t COMMAND_ON_AI = 0x03000;
 | 
			
		||||
const uint32_t COMMAND_COOL = 0x08000;
 | 
			
		||||
const uint32_t COMMAND_HEAT = 0x0C000;
 | 
			
		||||
// Commands
 | 
			
		||||
const uint32_t COMMAND_MASK = 0xFF000;
 | 
			
		||||
const uint32_t COMMAND_OFF = 0xC0000;
 | 
			
		||||
const uint32_t COMMAND_SWING = 0x10000;
 | 
			
		||||
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
 | 
			
		||||
const uint32_t COMMAND_AUTO = 0x0B000;
 | 
			
		||||
const uint32_t COMMAND_DRY_FAN = 0x09000;
 | 
			
		||||
 | 
			
		||||
const uint32_t COMMAND_MASK = 0xFF000;
 | 
			
		||||
const uint32_t COMMAND_ON_COOL = 0x00000;
 | 
			
		||||
const uint32_t COMMAND_ON_DRY = 0x01000;
 | 
			
		||||
const uint32_t COMMAND_ON_FAN_ONLY = 0x02000;
 | 
			
		||||
const uint32_t COMMAND_ON_AI = 0x03000;
 | 
			
		||||
const uint32_t COMMAND_ON_HEAT = 0x04000;
 | 
			
		||||
 | 
			
		||||
const uint32_t COMMAND_COOL = 0x08000;
 | 
			
		||||
const uint32_t COMMAND_DRY = 0x09000;
 | 
			
		||||
const uint32_t COMMAND_FAN_ONLY = 0x0A000;
 | 
			
		||||
const uint32_t COMMAND_AI = 0x0B000;
 | 
			
		||||
const uint32_t COMMAND_HEAT = 0x0C000;
 | 
			
		||||
 | 
			
		||||
// Fan speed
 | 
			
		||||
const uint32_t FAN_MASK = 0xF0;
 | 
			
		||||
const uint32_t FAN_AUTO = 0x50;
 | 
			
		||||
const uint32_t FAN_MIN = 0x00;
 | 
			
		||||
@@ -35,28 +41,28 @@ void LgIrClimate::transmit_state() {
 | 
			
		||||
  uint32_t remote_state = 0x8800000;
 | 
			
		||||
 | 
			
		||||
  // ESP_LOGD(TAG, "climate_lg_ir mode_before_ code: 0x%02X", modeBefore_);
 | 
			
		||||
 | 
			
		||||
  // Set command
 | 
			
		||||
  if (send_swing_cmd_) {
 | 
			
		||||
    send_swing_cmd_ = false;
 | 
			
		||||
    remote_state |= COMMAND_SWING;
 | 
			
		||||
  } else {
 | 
			
		||||
    if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
 | 
			
		||||
      remote_state |= COMMAND_ON_AI;
 | 
			
		||||
    } else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) {
 | 
			
		||||
      remote_state |= COMMAND_ON;
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_COOL;
 | 
			
		||||
    } else {
 | 
			
		||||
    bool climate_is_off = (mode_before_ == climate::CLIMATE_MODE_OFF);
 | 
			
		||||
    switch (this->mode) {
 | 
			
		||||
      case climate::CLIMATE_MODE_COOL:
 | 
			
		||||
          remote_state |= COMMAND_COOL;
 | 
			
		||||
          break;
 | 
			
		||||
        case climate::CLIMATE_MODE_HEAT:
 | 
			
		||||
          remote_state |= COMMAND_HEAT;
 | 
			
		||||
          break;
 | 
			
		||||
        case climate::CLIMATE_MODE_HEAT_COOL:
 | 
			
		||||
          remote_state |= COMMAND_AUTO;
 | 
			
		||||
        remote_state |= climate_is_off ? COMMAND_ON_COOL : COMMAND_COOL;
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_MODE_DRY:
 | 
			
		||||
          remote_state |= COMMAND_DRY_FAN;
 | 
			
		||||
        remote_state |= climate_is_off ? COMMAND_ON_DRY : COMMAND_DRY;
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_MODE_FAN_ONLY:
 | 
			
		||||
        remote_state |= climate_is_off ? COMMAND_ON_FAN_ONLY : COMMAND_FAN_ONLY;
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_MODE_HEAT_COOL:
 | 
			
		||||
        remote_state |= climate_is_off ? COMMAND_ON_AI : COMMAND_AI;
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_MODE_HEAT:
 | 
			
		||||
        remote_state |= climate_is_off ? COMMAND_ON_HEAT : COMMAND_HEAT;
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_MODE_OFF:
 | 
			
		||||
      default:
 | 
			
		||||
@@ -64,14 +70,15 @@ void LgIrClimate::transmit_state() {
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mode_before_ = this->mode;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode);
 | 
			
		||||
 | 
			
		||||
  // Set fan speed
 | 
			
		||||
  if (this->mode == climate::CLIMATE_MODE_OFF) {
 | 
			
		||||
    remote_state |= FAN_AUTO;
 | 
			
		||||
    } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY ||
 | 
			
		||||
               this->mode == climate::CLIMATE_MODE_HEAT) {
 | 
			
		||||
  } else {
 | 
			
		||||
    switch (this->fan_mode.value()) {
 | 
			
		||||
      case climate::CLIMATE_FAN_HIGH:
 | 
			
		||||
        remote_state |= FAN_MAX;
 | 
			
		||||
@@ -89,15 +96,12 @@ void LgIrClimate::transmit_state() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
			
		||||
      // remote_state |= FAN_MODE_AUTO_DRY;
 | 
			
		||||
    }
 | 
			
		||||
  // Set temperature
 | 
			
		||||
  if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
 | 
			
		||||
    auto temp = (uint8_t) roundf(clamp<float>(this->target_temperature, TEMP_MIN, TEMP_MAX));
 | 
			
		||||
    remote_state |= ((temp - 15) << TEMP_SHIFT);
 | 
			
		||||
  }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  transmit_(remote_state);
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
}
 | 
			
		||||
@@ -125,37 +129,42 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
 | 
			
		||||
  if ((remote_state & 0xFF00000) != 0x8800000)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  if ((remote_state & COMMAND_MASK) == COMMAND_ON) {
 | 
			
		||||
    this->mode = climate::CLIMATE_MODE_COOL;
 | 
			
		||||
  } else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) {
 | 
			
		||||
    this->mode = climate::CLIMATE_MODE_HEAT_COOL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Get command
 | 
			
		||||
  if ((remote_state & COMMAND_MASK) == COMMAND_OFF) {
 | 
			
		||||
    this->mode = climate::CLIMATE_MODE_OFF;
 | 
			
		||||
  } else if ((remote_state & COMMAND_MASK) == COMMAND_SWING) {
 | 
			
		||||
    this->swing_mode =
 | 
			
		||||
        this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
 | 
			
		||||
  } else {
 | 
			
		||||
    if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) {
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_HEAT_COOL;
 | 
			
		||||
    } else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) {
 | 
			
		||||
    switch (remote_state & COMMAND_MASK) {
 | 
			
		||||
      case COMMAND_DRY:
 | 
			
		||||
      case COMMAND_ON_DRY:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_DRY;
 | 
			
		||||
    } else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) {
 | 
			
		||||
        break;
 | 
			
		||||
      case COMMAND_FAN_ONLY:
 | 
			
		||||
      case COMMAND_ON_FAN_ONLY:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_FAN_ONLY;
 | 
			
		||||
        break;
 | 
			
		||||
      case COMMAND_AI:
 | 
			
		||||
      case COMMAND_ON_AI:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_HEAT_COOL;
 | 
			
		||||
        break;
 | 
			
		||||
      case COMMAND_HEAT:
 | 
			
		||||
      case COMMAND_ON_HEAT:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_HEAT;
 | 
			
		||||
    } else {
 | 
			
		||||
        break;
 | 
			
		||||
      case COMMAND_COOL:
 | 
			
		||||
      case COMMAND_ON_COOL:
 | 
			
		||||
      default:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_COOL;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Temperature
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT)
 | 
			
		||||
      this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
 | 
			
		||||
 | 
			
		||||
    // Fan Speed
 | 
			
		||||
    // Get fan speed
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
			
		||||
    } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT ||
 | 
			
		||||
               this->mode == climate::CLIMATE_MODE_DRY) {
 | 
			
		||||
    } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY ||
 | 
			
		||||
               this->mode == climate::CLIMATE_MODE_FAN_ONLY || this->mode == climate::CLIMATE_MODE_HEAT) {
 | 
			
		||||
      if ((remote_state & FAN_MASK) == FAN_AUTO) {
 | 
			
		||||
        this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
			
		||||
      } else if ((remote_state & FAN_MASK) == FAN_MIN) {
 | 
			
		||||
@@ -166,11 +175,17 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
 | 
			
		||||
        this->fan_mode = climate::CLIMATE_FAN_HIGH;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get temperature
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
 | 
			
		||||
      this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LgIrClimate::transmit_(uint32_t value) {
 | 
			
		||||
  calc_checksum_(value);
 | 
			
		||||
  ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02" PRIX32, value);
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ const uint8_t TEMP_MAX = 30;  // Celsius
 | 
			
		||||
class LgIrClimate : public climate_ir::ClimateIR {
 | 
			
		||||
 public:
 | 
			
		||||
  LgIrClimate()
 | 
			
		||||
      : climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, false,
 | 
			
		||||
      : climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, true,
 | 
			
		||||
                              {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
 | 
			
		||||
                               climate::CLIMATE_FAN_HIGH},
 | 
			
		||||
                              {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}
 | 
			
		||||
 
 | 
			
		||||
@@ -129,13 +129,13 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
 | 
			
		||||
   *
 | 
			
		||||
   * This is a legacy method and may be removed later, please use `.make_call()` instead.
 | 
			
		||||
   */
 | 
			
		||||
  ESPDEPRECATED("open() is deprecated, use make_call().set_command_open() instead.", "2021.9")
 | 
			
		||||
  ESPDEPRECATED("open() is deprecated, use make_call().set_command_open().perform() instead.", "2021.9")
 | 
			
		||||
  void open();
 | 
			
		||||
  /** Close the cover.
 | 
			
		||||
   *
 | 
			
		||||
   * This is a legacy method and may be removed later, please use `.make_call()` instead.
 | 
			
		||||
   */
 | 
			
		||||
  ESPDEPRECATED("close() is deprecated, use make_call().set_command_close() instead.", "2021.9")
 | 
			
		||||
  ESPDEPRECATED("close() is deprecated, use make_call().set_command_close().perform() instead.", "2021.9")
 | 
			
		||||
  void close();
 | 
			
		||||
  /** Stop the cover.
 | 
			
		||||
   *
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import CONF_ID, CONF_PIN
 | 
			
		||||
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
AUTO_LOAD = ["sensor"]
 | 
			
		||||
 | 
			
		||||
dallas_ns = cg.esphome_ns.namespace("dallas")
 | 
			
		||||
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(DallasComponent),
 | 
			
		||||
        cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.polling_component_schema("60s"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    pin = await cg.gpio_pin_expression(config[CONF_PIN])
 | 
			
		||||
    cg.add(var.set_pin(pin))
 | 
			
		||||
CONFIG_SCHEMA = cv.invalid(
 | 
			
		||||
    'The "dallas" component has been replaced by the "one_wire" component.\nhttps://esphome.io/components/one_wire'
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,287 +0,0 @@
 | 
			
		||||
#include "dallas_component.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dallas {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "dallas.sensor";
 | 
			
		||||
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS1822 = 0x22;
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS18B20 = 0x28;
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS1825 = 0x3B;
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS28EA00 = 0x42;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_START_CONVERSION = 0x44;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_READ_SCRATCH_PAD = 0xBE;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_WRITE_SCRATCH_PAD = 0x4E;
 | 
			
		||||
 | 
			
		||||
uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const {
 | 
			
		||||
  switch (this->resolution_) {
 | 
			
		||||
    case 9:
 | 
			
		||||
      return 94;
 | 
			
		||||
    case 10:
 | 
			
		||||
      return 188;
 | 
			
		||||
    case 11:
 | 
			
		||||
      return 375;
 | 
			
		||||
    default:
 | 
			
		||||
      return 750;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DallasComponent::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up DallasComponent...");
 | 
			
		||||
 | 
			
		||||
  pin_->setup();
 | 
			
		||||
 | 
			
		||||
  // clear bus with 480µs high, otherwise initial reset in search_vec() fails
 | 
			
		||||
  pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  delayMicroseconds(480);
 | 
			
		||||
 | 
			
		||||
  one_wire_ = new ESPOneWire(pin_);  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
 | 
			
		||||
  std::vector<uint64_t> raw_sensors;
 | 
			
		||||
  raw_sensors = this->one_wire_->search_vec();
 | 
			
		||||
 | 
			
		||||
  for (auto &address : raw_sensors) {
 | 
			
		||||
    auto *address8 = reinterpret_cast<uint8_t *>(&address);
 | 
			
		||||
    if (crc8(address8, 7) != address8[7]) {
 | 
			
		||||
      ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if (address8[0] != DALLAS_MODEL_DS18S20 && address8[0] != DALLAS_MODEL_DS1822 &&
 | 
			
		||||
        address8[0] != DALLAS_MODEL_DS18B20 && address8[0] != DALLAS_MODEL_DS1825 &&
 | 
			
		||||
        address8[0] != DALLAS_MODEL_DS28EA00) {
 | 
			
		||||
      ESP_LOGW(TAG, "Unknown device type 0x%02X.", address8[0]);
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    this->found_sensors_.push_back(address);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (auto *sensor : this->sensors_) {
 | 
			
		||||
    if (sensor->get_index().has_value()) {
 | 
			
		||||
      if (*sensor->get_index() >= this->found_sensors_.size()) {
 | 
			
		||||
        this->status_set_error("Sensor configured by index but not found");
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      sensor->set_address(this->found_sensors_[*sensor->get_index()]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!sensor->setup_sensor()) {
 | 
			
		||||
      this->status_set_error();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void DallasComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "DallasComponent:");
 | 
			
		||||
  LOG_PIN("  Pin: ", this->pin_);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
 | 
			
		||||
  if (this->found_sensors_.empty()) {
 | 
			
		||||
    ESP_LOGW(TAG, "  Found no sensors!");
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGD(TAG, "  Found sensors:");
 | 
			
		||||
    for (auto &address : this->found_sensors_) {
 | 
			
		||||
      ESP_LOGD(TAG, "    0x%s", format_hex(address).c_str());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (auto *sensor : this->sensors_) {
 | 
			
		||||
    LOG_SENSOR("  ", "Device", sensor);
 | 
			
		||||
    if (sensor->get_index().has_value()) {
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "    Index %u", *sensor->get_index());
 | 
			
		||||
      if (*sensor->get_index() >= this->found_sensors_.size()) {
 | 
			
		||||
        ESP_LOGE(TAG, "Couldn't find sensor by index - not connected. Proceeding without it.");
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Address: %s", sensor->get_address_name().c_str());
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Resolution: %u", sensor->get_resolution());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DallasComponent::register_sensor(DallasTemperatureSensor *sensor) { this->sensors_.push_back(sensor); }
 | 
			
		||||
void DallasComponent::update() {
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
 | 
			
		||||
  bool result;
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    result = this->one_wire_->reset();
 | 
			
		||||
  }
 | 
			
		||||
  if (!result) {
 | 
			
		||||
    if (!this->found_sensors_.empty()) {
 | 
			
		||||
      // Only log error if at the start sensors were found (and thus are disconnected during uptime)
 | 
			
		||||
      ESP_LOGE(TAG, "Requesting conversion failed");
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (auto *sensor : this->sensors_) {
 | 
			
		||||
      sensor->publish_state(NAN);
 | 
			
		||||
    }
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    this->one_wire_->skip();
 | 
			
		||||
    this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (auto *sensor : this->sensors_) {
 | 
			
		||||
    if (sensor->get_address() == 0) {
 | 
			
		||||
      ESP_LOGV(TAG, "'%s' - Indexed sensor not found at startup, skipping update", sensor->get_name().c_str());
 | 
			
		||||
      sensor->publish_state(NAN);
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] {
 | 
			
		||||
      bool res = sensor->read_scratch_pad();
 | 
			
		||||
 | 
			
		||||
      if (!res) {
 | 
			
		||||
        ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str());
 | 
			
		||||
        sensor->publish_state(NAN);
 | 
			
		||||
        this->status_set_warning();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (!sensor->check_scratch_pad()) {
 | 
			
		||||
        sensor->publish_state(NAN);
 | 
			
		||||
        this->status_set_warning();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      float tempc = sensor->get_temp_c();
 | 
			
		||||
      ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", sensor->get_name().c_str(), tempc);
 | 
			
		||||
      sensor->publish_state(tempc);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
 | 
			
		||||
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
 | 
			
		||||
void DallasTemperatureSensor::set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
 | 
			
		||||
optional<uint8_t> DallasTemperatureSensor::get_index() const { return this->index_; }
 | 
			
		||||
void DallasTemperatureSensor::set_index(uint8_t index) { this->index_ = index; }
 | 
			
		||||
uint8_t *DallasTemperatureSensor::get_address8() { return reinterpret_cast<uint8_t *>(&this->address_); }
 | 
			
		||||
uint64_t DallasTemperatureSensor::get_address() { return this->address_; }
 | 
			
		||||
 | 
			
		||||
const std::string &DallasTemperatureSensor::get_address_name() {
 | 
			
		||||
  if (this->address_name_.empty()) {
 | 
			
		||||
    this->address_name_ = std::string("0x") + format_hex(this->address_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return this->address_name_;
 | 
			
		||||
}
 | 
			
		||||
bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
 | 
			
		||||
  auto *wire = this->parent_->one_wire_;
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
 | 
			
		||||
    if (!wire->reset()) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    wire->select(this->address_);
 | 
			
		||||
    wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD);
 | 
			
		||||
 | 
			
		||||
    for (unsigned char &i : this->scratch_pad_) {
 | 
			
		||||
      i = wire->read8();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
bool DallasTemperatureSensor::setup_sensor() {
 | 
			
		||||
  bool r = this->read_scratch_pad();
 | 
			
		||||
 | 
			
		||||
  if (!r) {
 | 
			
		||||
    ESP_LOGE(TAG, "Reading scratchpad failed: reset");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->check_scratch_pad())
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  if (this->scratch_pad_[4] == this->resolution_)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  if (this->get_address8()[0] == DALLAS_MODEL_DS18S20) {
 | 
			
		||||
    // DS18S20 doesn't support resolution.
 | 
			
		||||
    ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (this->resolution_) {
 | 
			
		||||
    case 12:
 | 
			
		||||
      this->scratch_pad_[4] = 0x7F;
 | 
			
		||||
      break;
 | 
			
		||||
    case 11:
 | 
			
		||||
      this->scratch_pad_[4] = 0x5F;
 | 
			
		||||
      break;
 | 
			
		||||
    case 10:
 | 
			
		||||
      this->scratch_pad_[4] = 0x3F;
 | 
			
		||||
      break;
 | 
			
		||||
    case 9:
 | 
			
		||||
    default:
 | 
			
		||||
      this->scratch_pad_[4] = 0x1F;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto *wire = this->parent_->one_wire_;
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    if (wire->reset()) {
 | 
			
		||||
      wire->select(this->address_);
 | 
			
		||||
      wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
 | 
			
		||||
      wire->write8(this->scratch_pad_[2]);  // high alarm temp
 | 
			
		||||
      wire->write8(this->scratch_pad_[3]);  // low alarm temp
 | 
			
		||||
      wire->write8(this->scratch_pad_[4]);  // resolution
 | 
			
		||||
      wire->reset();
 | 
			
		||||
 | 
			
		||||
      // write value to EEPROM
 | 
			
		||||
      wire->select(this->address_);
 | 
			
		||||
      wire->write8(0x48);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  delay(20);  // allow it to finish operation
 | 
			
		||||
  wire->reset();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
bool DallasTemperatureSensor::check_scratch_pad() {
 | 
			
		||||
  bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
 | 
			
		||||
  bool config_validity = false;
 | 
			
		||||
 | 
			
		||||
  switch (this->get_address8()[0]) {
 | 
			
		||||
    case DALLAS_MODEL_DS18B20:
 | 
			
		||||
      config_validity = ((this->scratch_pad_[4] & 0x9F) == 0x1F);
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      config_validity = ((this->scratch_pad_[4] & 0x10) == 0x10);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef ESPHOME_LOG_LEVEL_VERY_VERBOSE
 | 
			
		||||
  ESP_LOGVV(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
 | 
			
		||||
            this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
 | 
			
		||||
            this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
 | 
			
		||||
            crc8(this->scratch_pad_, 8));
 | 
			
		||||
#endif
 | 
			
		||||
  if (!chksum_validity) {
 | 
			
		||||
    ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
 | 
			
		||||
  } else if (!config_validity) {
 | 
			
		||||
    ESP_LOGW(TAG, "'%s' - Scratch pad config register invalid!", this->get_name().c_str());
 | 
			
		||||
  }
 | 
			
		||||
  return chksum_validity && config_validity;
 | 
			
		||||
}
 | 
			
		||||
float DallasTemperatureSensor::get_temp_c() {
 | 
			
		||||
  int16_t temp = (int16_t(this->scratch_pad_[1]) << 11) | (int16_t(this->scratch_pad_[0]) << 3);
 | 
			
		||||
  if (this->get_address8()[0] == DALLAS_MODEL_DS18S20) {
 | 
			
		||||
    int diff = (this->scratch_pad_[7] - this->scratch_pad_[6]) << 7;
 | 
			
		||||
    temp = ((temp & 0xFFF0) << 3) - 16 + (diff / this->scratch_pad_[7]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return temp / 128.0f;
 | 
			
		||||
}
 | 
			
		||||
std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); }
 | 
			
		||||
 | 
			
		||||
}  // namespace dallas
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,79 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esp_one_wire.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dallas {
 | 
			
		||||
 | 
			
		||||
class DallasTemperatureSensor;
 | 
			
		||||
 | 
			
		||||
class DallasComponent : public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
 | 
			
		||||
  void register_sensor(DallasTemperatureSensor *sensor);
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  friend DallasTemperatureSensor;
 | 
			
		||||
 | 
			
		||||
  InternalGPIOPin *pin_;
 | 
			
		||||
  ESPOneWire *one_wire_;
 | 
			
		||||
  std::vector<DallasTemperatureSensor *> sensors_;
 | 
			
		||||
  std::vector<uint64_t> found_sensors_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Internal class that helps us create multiple sensors for one Dallas hub.
 | 
			
		||||
class DallasTemperatureSensor : public sensor::Sensor {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_parent(DallasComponent *parent) { parent_ = parent; }
 | 
			
		||||
  /// Helper to get a pointer to the address as uint8_t.
 | 
			
		||||
  uint8_t *get_address8();
 | 
			
		||||
  uint64_t get_address();
 | 
			
		||||
  /// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
 | 
			
		||||
  const std::string &get_address_name();
 | 
			
		||||
 | 
			
		||||
  /// Set the 64-bit unsigned address for this sensor.
 | 
			
		||||
  void set_address(uint64_t address);
 | 
			
		||||
  /// Get the index of this sensor. (0 if using address.)
 | 
			
		||||
  optional<uint8_t> get_index() const;
 | 
			
		||||
  /// Set the index of this sensor. If using index, address will be set after setup.
 | 
			
		||||
  void set_index(uint8_t index);
 | 
			
		||||
  /// Get the set resolution for this sensor.
 | 
			
		||||
  uint8_t get_resolution() const;
 | 
			
		||||
  /// Set the resolution for this sensor.
 | 
			
		||||
  void set_resolution(uint8_t resolution);
 | 
			
		||||
  /// Get the number of milliseconds we have to wait for the conversion phase.
 | 
			
		||||
  uint16_t millis_to_wait_for_conversion() const;
 | 
			
		||||
 | 
			
		||||
  bool setup_sensor();
 | 
			
		||||
  bool read_scratch_pad();
 | 
			
		||||
 | 
			
		||||
  bool check_scratch_pad();
 | 
			
		||||
 | 
			
		||||
  float get_temp_c();
 | 
			
		||||
 | 
			
		||||
  std::string unique_id() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DallasComponent *parent_;
 | 
			
		||||
  uint64_t address_;
 | 
			
		||||
  optional<uint8_t> index_;
 | 
			
		||||
 | 
			
		||||
  uint8_t resolution_;
 | 
			
		||||
  std::string address_name_;
 | 
			
		||||
  uint8_t scratch_pad_[9] = {
 | 
			
		||||
      0,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace dallas
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,252 +0,0 @@
 | 
			
		||||
#include "esp_one_wire.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dallas {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "dallas.one_wire";
 | 
			
		||||
 | 
			
		||||
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
 | 
			
		||||
const int ONE_WIRE_ROM_SEARCH = 0xF0;
 | 
			
		||||
 | 
			
		||||
ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); }
 | 
			
		||||
 | 
			
		||||
bool HOT IRAM_ATTR ESPOneWire::reset() {
 | 
			
		||||
  // See reset here:
 | 
			
		||||
  // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
 | 
			
		||||
  // Wait for communication to clear (delay G)
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  uint8_t retries = 125;
 | 
			
		||||
  do {
 | 
			
		||||
    if (--retries == 0)
 | 
			
		||||
      return false;
 | 
			
		||||
    delayMicroseconds(2);
 | 
			
		||||
  } while (!pin_.digital_read());
 | 
			
		||||
 | 
			
		||||
  // Send 480µs LOW TX reset pulse (drive bus low, delay H)
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
  delayMicroseconds(480);
 | 
			
		||||
 | 
			
		||||
  // Release the bus, delay I
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  delayMicroseconds(70);
 | 
			
		||||
 | 
			
		||||
  // sample bus, 0=device(s) present, 1=no device present
 | 
			
		||||
  bool r = !pin_.digital_read();
 | 
			
		||||
  // delay J
 | 
			
		||||
  delayMicroseconds(410);
 | 
			
		||||
  return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) {
 | 
			
		||||
  // drive bus low
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
 | 
			
		||||
  // from datasheet:
 | 
			
		||||
  // write 0 low time: t_low0: min=60µs, max=120µs
 | 
			
		||||
  // write 1 low time: t_low1: min=1µs, max=15µs
 | 
			
		||||
  // time slot: t_slot: min=60µs, max=120µs
 | 
			
		||||
  // recovery time: t_rec: min=1µs
 | 
			
		||||
  // ds18b20 appears to read the bus after roughly 14µs
 | 
			
		||||
  uint32_t delay0 = bit ? 6 : 60;
 | 
			
		||||
  uint32_t delay1 = bit ? 54 : 5;
 | 
			
		||||
 | 
			
		||||
  // delay A/C
 | 
			
		||||
  delayMicroseconds(delay0);
 | 
			
		||||
  // release bus
 | 
			
		||||
  pin_.digital_write(true);
 | 
			
		||||
  // delay B/D
 | 
			
		||||
  delayMicroseconds(delay1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HOT IRAM_ATTR ESPOneWire::read_bit() {
 | 
			
		||||
  // drive bus low
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
 | 
			
		||||
  // note: for reading we'll need very accurate timing, as the
 | 
			
		||||
  // timing for the digital_read() is tight; according to the datasheet,
 | 
			
		||||
  // we should read at the end of 16µs starting from the bus low
 | 
			
		||||
  // typically, the ds18b20 pulls the line high after 11µs for a logical 1
 | 
			
		||||
  // and 29µs for a logical 0
 | 
			
		||||
 | 
			
		||||
  uint32_t start = micros();
 | 
			
		||||
  // datasheet says >1µs
 | 
			
		||||
  delayMicroseconds(3);
 | 
			
		||||
 | 
			
		||||
  // release bus, delay E
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
 | 
			
		||||
  // Unfortunately some frameworks have different characteristics than others
 | 
			
		||||
  // esp32 arduino appears to pull the bus low only after the digital_write(false),
 | 
			
		||||
  // whereas on esp-idf it already happens during the pin_mode(OUTPUT)
 | 
			
		||||
  // manually correct for this with these constants.
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  uint32_t timing_constant = 12;
 | 
			
		||||
#else
 | 
			
		||||
  uint32_t timing_constant = 14;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  // measure from start value directly, to get best accurate timing no matter
 | 
			
		||||
  // how long pin_mode/delayMicroseconds took
 | 
			
		||||
  while (micros() - start < timing_constant)
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
  // sample bus to read bit from peer
 | 
			
		||||
  bool r = pin_.digital_read();
 | 
			
		||||
 | 
			
		||||
  // read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
 | 
			
		||||
  uint32_t now = micros();
 | 
			
		||||
  if (now - start < 60)
 | 
			
		||||
    delayMicroseconds(60 - (now - start));
 | 
			
		||||
 | 
			
		||||
  return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR ESPOneWire::write8(uint8_t val) {
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    this->write_bit(bool((1u << i) & val));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR ESPOneWire::write64(uint64_t val) {
 | 
			
		||||
  for (uint8_t i = 0; i < 64; i++) {
 | 
			
		||||
    this->write_bit(bool((1ULL << i) & val));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t IRAM_ATTR ESPOneWire::read8() {
 | 
			
		||||
  uint8_t ret = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    ret |= (uint8_t(this->read_bit()) << i);
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
uint64_t IRAM_ATTR ESPOneWire::read64() {
 | 
			
		||||
  uint64_t ret = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    ret |= (uint64_t(this->read_bit()) << i);
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ESPOneWire::select(uint64_t address) {
 | 
			
		||||
  this->write8(ONE_WIRE_ROM_SELECT);
 | 
			
		||||
  this->write64(address);
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ESPOneWire::reset_search() {
 | 
			
		||||
  this->last_discrepancy_ = 0;
 | 
			
		||||
  this->last_device_flag_ = false;
 | 
			
		||||
  this->rom_number_ = 0;
 | 
			
		||||
}
 | 
			
		||||
uint64_t IRAM_ATTR ESPOneWire::search() {
 | 
			
		||||
  if (this->last_device_flag_) {
 | 
			
		||||
    return 0u;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    if (!this->reset()) {
 | 
			
		||||
      // Reset failed or no devices present
 | 
			
		||||
      this->reset_search();
 | 
			
		||||
      return 0u;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t id_bit_number = 1;
 | 
			
		||||
  uint8_t last_zero = 0;
 | 
			
		||||
  uint8_t rom_byte_number = 0;
 | 
			
		||||
  bool search_result = false;
 | 
			
		||||
  uint8_t rom_byte_mask = 1;
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    // Initiate search
 | 
			
		||||
    this->write8(ONE_WIRE_ROM_SEARCH);
 | 
			
		||||
    do {
 | 
			
		||||
      // read bit
 | 
			
		||||
      bool id_bit = this->read_bit();
 | 
			
		||||
      // read its complement
 | 
			
		||||
      bool cmp_id_bit = this->read_bit();
 | 
			
		||||
 | 
			
		||||
      if (id_bit && cmp_id_bit) {
 | 
			
		||||
        // No devices participating in search
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      bool branch;
 | 
			
		||||
 | 
			
		||||
      if (id_bit != cmp_id_bit) {
 | 
			
		||||
        // only chose one branch, the other one doesn't have any devices.
 | 
			
		||||
        branch = id_bit;
 | 
			
		||||
      } else {
 | 
			
		||||
        // there are devices with both 0s and 1s at this bit
 | 
			
		||||
        if (id_bit_number < this->last_discrepancy_) {
 | 
			
		||||
          branch = (this->rom_number8_()[rom_byte_number] & rom_byte_mask) > 0;
 | 
			
		||||
        } else {
 | 
			
		||||
          branch = id_bit_number == this->last_discrepancy_;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!branch) {
 | 
			
		||||
          last_zero = id_bit_number;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (branch) {
 | 
			
		||||
        // set bit
 | 
			
		||||
        this->rom_number8_()[rom_byte_number] |= rom_byte_mask;
 | 
			
		||||
      } else {
 | 
			
		||||
        // clear bit
 | 
			
		||||
        this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // choose/announce branch
 | 
			
		||||
      this->write_bit(branch);
 | 
			
		||||
      id_bit_number++;
 | 
			
		||||
      rom_byte_mask <<= 1;
 | 
			
		||||
      if (rom_byte_mask == 0u) {
 | 
			
		||||
        // go to next byte
 | 
			
		||||
        rom_byte_number++;
 | 
			
		||||
        rom_byte_mask = 1;
 | 
			
		||||
      }
 | 
			
		||||
    } while (rom_byte_number < 8);  // loop through all bytes
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (id_bit_number >= 65) {
 | 
			
		||||
    this->last_discrepancy_ = last_zero;
 | 
			
		||||
    if (this->last_discrepancy_ == 0) {
 | 
			
		||||
      // we're at root and have no choices left, so this was the last one.
 | 
			
		||||
      this->last_device_flag_ = true;
 | 
			
		||||
    }
 | 
			
		||||
    search_result = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  search_result = search_result && (this->rom_number8_()[0] != 0);
 | 
			
		||||
  if (!search_result) {
 | 
			
		||||
    this->reset_search();
 | 
			
		||||
    return 0u;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return this->rom_number_;
 | 
			
		||||
}
 | 
			
		||||
std::vector<uint64_t> ESPOneWire::search_vec() {
 | 
			
		||||
  std::vector<uint64_t> res;
 | 
			
		||||
 | 
			
		||||
  this->reset_search();
 | 
			
		||||
  uint64_t address;
 | 
			
		||||
  while ((address = this->search()) != 0u)
 | 
			
		||||
    res.push_back(address);
 | 
			
		||||
 | 
			
		||||
  return res;
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ESPOneWire::skip() {
 | 
			
		||||
  this->write8(0xCC);  // skip ROM
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast<uint8_t *>(&this->rom_number_); }
 | 
			
		||||
 | 
			
		||||
}  // namespace dallas
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,68 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dallas {
 | 
			
		||||
 | 
			
		||||
extern const uint8_t ONE_WIRE_ROM_SELECT;
 | 
			
		||||
extern const int ONE_WIRE_ROM_SEARCH;
 | 
			
		||||
 | 
			
		||||
class ESPOneWire {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit ESPOneWire(InternalGPIOPin *pin);
 | 
			
		||||
 | 
			
		||||
  /** Reset the bus, should be done before all write operations.
 | 
			
		||||
   *
 | 
			
		||||
   * Takes approximately 1ms.
 | 
			
		||||
   *
 | 
			
		||||
   * @return Whether the operation was successful.
 | 
			
		||||
   */
 | 
			
		||||
  bool reset();
 | 
			
		||||
 | 
			
		||||
  /// Write a single bit to the bus, takes about 70µs.
 | 
			
		||||
  void write_bit(bool bit);
 | 
			
		||||
 | 
			
		||||
  /// Read a single bit from the bus, takes about 70µs
 | 
			
		||||
  bool read_bit();
 | 
			
		||||
 | 
			
		||||
  /// Write a word to the bus. LSB first.
 | 
			
		||||
  void write8(uint8_t val);
 | 
			
		||||
 | 
			
		||||
  /// Write a 64 bit unsigned integer to the bus. LSB first.
 | 
			
		||||
  void write64(uint64_t val);
 | 
			
		||||
 | 
			
		||||
  /// Write a command to the bus that addresses all devices by skipping the ROM.
 | 
			
		||||
  void skip();
 | 
			
		||||
 | 
			
		||||
  /// Read an 8 bit word from the bus.
 | 
			
		||||
  uint8_t read8();
 | 
			
		||||
 | 
			
		||||
  /// Read an 64-bit unsigned integer from the bus.
 | 
			
		||||
  uint64_t read64();
 | 
			
		||||
 | 
			
		||||
  /// Select a specific address on the bus for the following command.
 | 
			
		||||
  void select(uint64_t address);
 | 
			
		||||
 | 
			
		||||
  /// Reset the device search.
 | 
			
		||||
  void reset_search();
 | 
			
		||||
 | 
			
		||||
  /// Search for a 1-Wire device on the bus. Returns 0 if all devices have been found.
 | 
			
		||||
  uint64_t search();
 | 
			
		||||
 | 
			
		||||
  /// Helper that wraps search in a std::vector.
 | 
			
		||||
  std::vector<uint64_t> search_vec();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  /// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer.
 | 
			
		||||
  inline uint8_t *rom_number8_();
 | 
			
		||||
 | 
			
		||||
  ISRInternalGPIOPin pin_;
 | 
			
		||||
  uint8_t last_discrepancy_{0};
 | 
			
		||||
  bool last_device_flag_{false};
 | 
			
		||||
  uint64_t rom_number_{0};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace dallas
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,50 +1,5 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ADDRESS,
 | 
			
		||||
    CONF_DALLAS_ID,
 | 
			
		||||
    CONF_INDEX,
 | 
			
		||||
    CONF_RESOLUTION,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.invalid(
 | 
			
		||||
    'The "dallas" sensor is now "dallas_temp"\nhttps://esphome.io/components/sensor/dallas_temp'
 | 
			
		||||
)
 | 
			
		||||
from . import DallasComponent, dallas_ns
 | 
			
		||||
 | 
			
		||||
DallasTemperatureSensor = dallas_ns.class_("DallasTemperatureSensor", sensor.Sensor)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    sensor.sensor_schema(
 | 
			
		||||
        DallasTemperatureSensor,
 | 
			
		||||
        unit_of_measurement=UNIT_CELSIUS,
 | 
			
		||||
        accuracy_decimals=1,
 | 
			
		||||
        device_class=DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
        state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    ).extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent),
 | 
			
		||||
            cv.Optional(CONF_ADDRESS): cv.hex_uint64_t,
 | 
			
		||||
            cv.Optional(CONF_INDEX): cv.positive_int,
 | 
			
		||||
            cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_DALLAS_ID])
 | 
			
		||||
    var = await sensor.new_sensor(config)
 | 
			
		||||
 | 
			
		||||
    if CONF_ADDRESS in config:
 | 
			
		||||
        cg.add(var.set_address(config[CONF_ADDRESS]))
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_index(config[CONF_INDEX]))
 | 
			
		||||
 | 
			
		||||
    if CONF_RESOLUTION in config:
 | 
			
		||||
        cg.add(var.set_resolution(config[CONF_RESOLUTION]))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_parent(hub))
 | 
			
		||||
 | 
			
		||||
    cg.add(hub.register_sensor(var))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								esphome/components/dallas_temp/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/dallas_temp/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
CODEOWNERS = ["@ssieb"]
 | 
			
		||||
							
								
								
									
										172
									
								
								esphome/components/dallas_temp/dallas_temp.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								esphome/components/dallas_temp/dallas_temp.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,172 @@
 | 
			
		||||
#include "dallas_temp.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dallas_temp {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "dallas.temp.sensor";
 | 
			
		||||
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_START_CONVERSION = 0x44;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_READ_SCRATCH_PAD = 0xBE;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_WRITE_SCRATCH_PAD = 0x4E;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_COPY_SCRATCH_PAD = 0x48;
 | 
			
		||||
 | 
			
		||||
uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion_() const {
 | 
			
		||||
  switch (this->resolution_) {
 | 
			
		||||
    case 9:
 | 
			
		||||
      return 94;
 | 
			
		||||
    case 10:
 | 
			
		||||
      return 188;
 | 
			
		||||
    case 11:
 | 
			
		||||
      return 375;
 | 
			
		||||
    default:
 | 
			
		||||
      return 750;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DallasTemperatureSensor::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Dallas Temperature Sensor:");
 | 
			
		||||
  if (this->address_ == 0) {
 | 
			
		||||
    ESP_LOGW(TAG, "  Unable to select an address");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  LOG_ONE_WIRE_DEVICE(this);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Resolution: %u bits", this->resolution_);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DallasTemperatureSensor::update() {
 | 
			
		||||
  if (this->address_ == 0)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
 | 
			
		||||
  this->send_command_(DALLAS_COMMAND_START_CONVERSION);
 | 
			
		||||
 | 
			
		||||
  this->set_timeout(this->get_address_name(), this->millis_to_wait_for_conversion_(), [this] {
 | 
			
		||||
    if (!this->read_scratch_pad_() || !this->check_scratch_pad_()) {
 | 
			
		||||
      this->publish_state(NAN);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    float tempc = this->get_temp_c_();
 | 
			
		||||
    ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", this->get_name().c_str(), tempc);
 | 
			
		||||
    this->publish_state(tempc);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR DallasTemperatureSensor::read_scratch_pad_int_() {
 | 
			
		||||
  for (uint8_t &i : this->scratch_pad_) {
 | 
			
		||||
    i = this->bus_->read8();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DallasTemperatureSensor::read_scratch_pad_() {
 | 
			
		||||
  bool success;
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    success = this->send_command_(DALLAS_COMMAND_READ_SCRATCH_PAD);
 | 
			
		||||
    if (success)
 | 
			
		||||
      this->read_scratch_pad_int_();
 | 
			
		||||
  }
 | 
			
		||||
  if (!success) {
 | 
			
		||||
    ESP_LOGW(TAG, "'%s' - reading scratch pad failed bus reset", this->get_name().c_str());
 | 
			
		||||
    this->status_set_warning("bus reset failed");
 | 
			
		||||
  }
 | 
			
		||||
  return success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DallasTemperatureSensor::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "setting up Dallas temperature sensor...");
 | 
			
		||||
  if (!this->check_address_())
 | 
			
		||||
    return;
 | 
			
		||||
  if (!this->read_scratch_pad_())
 | 
			
		||||
    return;
 | 
			
		||||
  if (!this->check_scratch_pad_())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
 | 
			
		||||
    // DS18S20 doesn't support resolution.
 | 
			
		||||
    ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t res;
 | 
			
		||||
  switch (this->resolution_) {
 | 
			
		||||
    case 12:
 | 
			
		||||
      res = 0x7F;
 | 
			
		||||
      break;
 | 
			
		||||
    case 11:
 | 
			
		||||
      res = 0x5F;
 | 
			
		||||
      break;
 | 
			
		||||
    case 10:
 | 
			
		||||
      res = 0x3F;
 | 
			
		||||
      break;
 | 
			
		||||
    case 9:
 | 
			
		||||
    default:
 | 
			
		||||
      res = 0x1F;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->scratch_pad_[4] == res)
 | 
			
		||||
    return;
 | 
			
		||||
  this->scratch_pad_[4] = res;
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    if (this->send_command_(DALLAS_COMMAND_WRITE_SCRATCH_PAD)) {
 | 
			
		||||
      this->bus_->write8(this->scratch_pad_[2]);  // high alarm temp
 | 
			
		||||
      this->bus_->write8(this->scratch_pad_[3]);  // low alarm temp
 | 
			
		||||
      this->bus_->write8(this->scratch_pad_[4]);  // resolution
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // write value to EEPROM
 | 
			
		||||
    this->send_command_(DALLAS_COMMAND_COPY_SCRATCH_PAD);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DallasTemperatureSensor::check_scratch_pad_() {
 | 
			
		||||
  bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
 | 
			
		||||
 | 
			
		||||
#ifdef ESPHOME_LOG_LEVEL_VERY_VERBOSE
 | 
			
		||||
  ESP_LOGVV(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
 | 
			
		||||
            this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
 | 
			
		||||
            this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
 | 
			
		||||
            crc8(this->scratch_pad_, 8));
 | 
			
		||||
#endif
 | 
			
		||||
  if (!chksum_validity) {
 | 
			
		||||
    ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
 | 
			
		||||
    this->status_set_warning("scratch pad checksum invalid");
 | 
			
		||||
  }
 | 
			
		||||
  return chksum_validity;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float DallasTemperatureSensor::get_temp_c_() {
 | 
			
		||||
  int16_t temp = (this->scratch_pad_[1] << 8) | this->scratch_pad_[0];
 | 
			
		||||
  if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
 | 
			
		||||
    if (this->scratch_pad_[7] != 0x10)
 | 
			
		||||
      ESP_LOGE(TAG, "unexpected COUNT_PER_C value: %u", this->scratch_pad_[7]);
 | 
			
		||||
    temp = ((temp & 0xfff7) << 3) + (0x10 - this->scratch_pad_[6]) - 4;
 | 
			
		||||
  } else {
 | 
			
		||||
    switch (this->resolution_) {
 | 
			
		||||
      case 9:
 | 
			
		||||
        temp &= 0xfff8;
 | 
			
		||||
        break;
 | 
			
		||||
      case 10:
 | 
			
		||||
        temp &= 0xfffc;
 | 
			
		||||
        break;
 | 
			
		||||
      case 11:
 | 
			
		||||
        temp &= 0xfffe;
 | 
			
		||||
        break;
 | 
			
		||||
      case 12:
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return temp / 16.0f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace dallas_temp
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										32
									
								
								esphome/components/dallas_temp/dallas_temp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								esphome/components/dallas_temp/dallas_temp.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/one_wire/one_wire.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dallas_temp {
 | 
			
		||||
 | 
			
		||||
class DallasTemperatureSensor : public PollingComponent, public sensor::Sensor, public one_wire::OneWireDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  /// Set the resolution for this sensor.
 | 
			
		||||
  void set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t resolution_;
 | 
			
		||||
  uint8_t scratch_pad_[9] = {0};
 | 
			
		||||
 | 
			
		||||
  /// Get the number of milliseconds we have to wait for the conversion phase.
 | 
			
		||||
  uint16_t millis_to_wait_for_conversion_() const;
 | 
			
		||||
  bool read_scratch_pad_();
 | 
			
		||||
  void read_scratch_pad_int_();
 | 
			
		||||
  bool check_scratch_pad_();
 | 
			
		||||
  float get_temp_c_();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace dallas_temp
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										43
									
								
								esphome/components/dallas_temp/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								esphome/components/dallas_temp/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import one_wire, sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_RESOLUTION,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
dallas_temp_ns = cg.esphome_ns.namespace("dallas_temp")
 | 
			
		||||
 | 
			
		||||
DallasTemperatureSensor = dallas_temp_ns.class_(
 | 
			
		||||
    "DallasTemperatureSensor",
 | 
			
		||||
    cg.PollingComponent,
 | 
			
		||||
    sensor.Sensor,
 | 
			
		||||
    one_wire.OneWireDevice,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    sensor.sensor_schema(
 | 
			
		||||
        DallasTemperatureSensor,
 | 
			
		||||
        unit_of_measurement=UNIT_CELSIUS,
 | 
			
		||||
        accuracy_decimals=1,
 | 
			
		||||
        device_class=DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
        state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    )
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(one_wire.one_wire_device_schema())
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await sensor.new_sensor(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await one_wire.register_one_wire_device(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_resolution(config[CONF_RESOLUTION]))
 | 
			
		||||
@@ -80,6 +80,17 @@ void DateCall::validate_() {
 | 
			
		||||
 | 
			
		||||
void DateCall::perform() {
 | 
			
		||||
  this->validate_();
 | 
			
		||||
  ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
 | 
			
		||||
 | 
			
		||||
  if (this->year_.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, " Year: %d", *this->year_);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->month_.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, " Month: %d", *this->month_);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->day_.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, " Day: %d", *this->day_);
 | 
			
		||||
  }
 | 
			
		||||
  this->parent_->control(*this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_na
 | 
			
		||||
uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); }
 | 
			
		||||
 | 
			
		||||
void DebugComponent::get_device_info_(std::string &device_info) {
 | 
			
		||||
  str::string reset_reason = get_reset_reason_();
 | 
			
		||||
  std::string reset_reason = get_reset_reason_();
 | 
			
		||||
  ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version());
 | 
			
		||||
  ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz());
 | 
			
		||||
  ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id());
 | 
			
		||||
 
 | 
			
		||||
@@ -34,10 +34,12 @@ enum WakeupPinMode {
 | 
			
		||||
  WAKEUP_PIN_MODE_INVERT_WAKEUP,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
 | 
			
		||||
struct Ext1Wakeup {
 | 
			
		||||
  uint64_t mask;
 | 
			
		||||
  esp_sleep_ext1_wakeup_mode_t wakeup_mode;
 | 
			
		||||
};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
struct WakeupCauseToRunDuration {
 | 
			
		||||
  // Run duration if woken up by timer or any other reason besides those below.
 | 
			
		||||
@@ -114,7 +116,11 @@ class DeepSleepComponent : public Component {
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  InternalGPIOPin *wakeup_pin_;
 | 
			
		||||
  WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
 | 
			
		||||
 | 
			
		||||
#if !defined(USE_ESP32_VARIANT_ESP32C3)
 | 
			
		||||
  optional<Ext1Wakeup> ext1_wakeup_;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  optional<bool> touch_wakeup_;
 | 
			
		||||
  optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -96,16 +96,16 @@ def get_board(core_obj=None):
 | 
			
		||||
def get_download_types(storage_json):
 | 
			
		||||
    return [
 | 
			
		||||
        {
 | 
			
		||||
            "title": "Modern format",
 | 
			
		||||
            "title": "Factory format (Previously Modern)",
 | 
			
		||||
            "description": "For use with ESPHome Web and other tools.",
 | 
			
		||||
            "file": "firmware-factory.bin",
 | 
			
		||||
            "download": f"{storage_json.name}-factory.bin",
 | 
			
		||||
            "file": "firmware.factory.bin",
 | 
			
		||||
            "download": f"{storage_json.name}.factory.bin",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "title": "Legacy format",
 | 
			
		||||
            "description": "For use with ESPHome Flasher.",
 | 
			
		||||
            "file": "firmware.bin",
 | 
			
		||||
            "download": f"{storage_json.name}.bin",
 | 
			
		||||
            "title": "OTA format (Previously Legacy)",
 | 
			
		||||
            "description": "For OTA updating a device.",
 | 
			
		||||
            "file": "firmware.ota.bin",
 | 
			
		||||
            "download": f"{storage_json.name}.ota.bin",
 | 
			
		||||
        },
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,17 +17,19 @@ from SCons.Script import ARGUMENTS
 | 
			
		||||
 | 
			
		||||
# Copy over the default sdkconfig.
 | 
			
		||||
from os import path
 | 
			
		||||
 | 
			
		||||
if path.exists("./sdkconfig.defaults"):
 | 
			
		||||
    os.makedirs(".temp", exist_ok=True)
 | 
			
		||||
    shutil.copy("./sdkconfig.defaults", "./.temp/sdkconfig-esp32-idf")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esp32_create_combined_bin(source, target, env):
 | 
			
		||||
    verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0")))
 | 
			
		||||
    if verbose:
 | 
			
		||||
        print("Generating combined binary for serial flashing")
 | 
			
		||||
    app_offset = 0x10000
 | 
			
		||||
 | 
			
		||||
    new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
 | 
			
		||||
    new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
 | 
			
		||||
    sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
 | 
			
		||||
    firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
 | 
			
		||||
    chip = env.get("BOARD_MCU")
 | 
			
		||||
@@ -62,5 +64,14 @@ def esp32_create_combined_bin(source, target, env):
 | 
			
		||||
    else:
 | 
			
		||||
        subprocess.run(["esptool.py", *cmd])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esp32_copy_ota_bin(source, target, env):
 | 
			
		||||
    firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
 | 
			
		||||
    new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.ota.bin")
 | 
			
		||||
 | 
			
		||||
    shutil.copyfile(firmware_name, new_file_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# pylint: disable=E0602
 | 
			
		||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)  # noqa
 | 
			
		||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin)  # noqa
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_VSYNC_PIN,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option
 | 
			
		||||
from esphome.components.esp32 import add_idf_component
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32"]
 | 
			
		||||
@@ -290,8 +290,11 @@ async def to_code(config):
 | 
			
		||||
    cg.add_define("USE_ESP32_CAMERA")
 | 
			
		||||
 | 
			
		||||
    if CORE.using_esp_idf:
 | 
			
		||||
        cg.add_library("espressif/esp32-camera", "1.0.0")
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True)
 | 
			
		||||
        add_idf_component(
 | 
			
		||||
            name="esp32-camera",
 | 
			
		||||
            repo="https://github.com/espressif/esp32-camera.git",
 | 
			
		||||
            ref="v2.0.9",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_STREAM_START, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,18 @@ Import("env")  # noqa
 | 
			
		||||
 | 
			
		||||
def esp8266_copy_factory_bin(source, target, env):
 | 
			
		||||
    firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
 | 
			
		||||
    new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
 | 
			
		||||
    new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
 | 
			
		||||
 | 
			
		||||
    shutil.copyfile(firmware_name, new_file_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esp8266_copy_ota_bin(source, target, env):
 | 
			
		||||
    firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
 | 
			
		||||
    new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.ota.bin")
 | 
			
		||||
 | 
			
		||||
    shutil.copyfile(firmware_name, new_file_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# pylint: disable=E0602
 | 
			
		||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp8266_copy_factory_bin)  # noqa
 | 
			
		||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp8266_copy_ota_bin)  # noqa
 | 
			
		||||
 
 | 
			
		||||
@@ -631,7 +631,7 @@ void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister regi
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
 | 
			
		||||
 | 
			
		||||
  if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
 | 
			
		||||
    ESP_LOGD(TAG, "Select PHY Register Page 0x%02" PRIX32, 0x0);
 | 
			
		||||
    ESP_LOGD(TAG, "Select PHY Register Page 0x00");
 | 
			
		||||
    err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0);
 | 
			
		||||
    ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed");
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								esphome/components/gpio/one_wire/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphome/components/gpio/one_wire/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import CONF_ID, CONF_PIN
 | 
			
		||||
from esphome.components.one_wire import OneWireBus
 | 
			
		||||
from .. import gpio_ns
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@ssieb"]
 | 
			
		||||
 | 
			
		||||
GPIOOneWireBus = gpio_ns.class_("GPIOOneWireBus", OneWireBus, cg.Component)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(GPIOOneWireBus),
 | 
			
		||||
        cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    pin = await cg.gpio_pin_expression(config[CONF_PIN])
 | 
			
		||||
    cg.add(var.set_pin(pin))
 | 
			
		||||
							
								
								
									
										203
									
								
								esphome/components/gpio/one_wire/gpio_one_wire.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								esphome/components/gpio/one_wire/gpio_one_wire.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,203 @@
 | 
			
		||||
#include "gpio_one_wire.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace gpio {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "gpio.one_wire";
 | 
			
		||||
 | 
			
		||||
void GPIOOneWireBus::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up 1-wire bus...");
 | 
			
		||||
  this->t_pin_->setup();
 | 
			
		||||
  // clear bus with 480µs high, otherwise initial reset in search might fail
 | 
			
		||||
  this->t_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  delayMicroseconds(480);
 | 
			
		||||
  this->search();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GPIOOneWireBus::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "GPIO 1-wire bus:");
 | 
			
		||||
  LOG_PIN("  Pin: ", this->t_pin_);
 | 
			
		||||
  this->dump_devices_(TAG);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HOT IRAM_ATTR GPIOOneWireBus::reset() {
 | 
			
		||||
  // See reset here:
 | 
			
		||||
  // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
 | 
			
		||||
  // Wait for communication to clear (delay G)
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  uint8_t retries = 125;
 | 
			
		||||
  do {
 | 
			
		||||
    if (--retries == 0)
 | 
			
		||||
      return false;
 | 
			
		||||
    delayMicroseconds(2);
 | 
			
		||||
  } while (!pin_.digital_read());
 | 
			
		||||
 | 
			
		||||
  bool r;
 | 
			
		||||
 | 
			
		||||
  // Send 480µs LOW TX reset pulse (drive bus low, delay H)
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
  delayMicroseconds(480);
 | 
			
		||||
 | 
			
		||||
  // Release the bus, delay I
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  delayMicroseconds(70);
 | 
			
		||||
 | 
			
		||||
  // sample bus, 0=device(s) present, 1=no device present
 | 
			
		||||
  r = !pin_.digital_read();
 | 
			
		||||
  // delay J
 | 
			
		||||
  delayMicroseconds(410);
 | 
			
		||||
  return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HOT IRAM_ATTR GPIOOneWireBus::write_bit_(bool bit) {
 | 
			
		||||
  // drive bus low
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
 | 
			
		||||
  // from datasheet:
 | 
			
		||||
  // write 0 low time: t_low0: min=60µs, max=120µs
 | 
			
		||||
  // write 1 low time: t_low1: min=1µs, max=15µs
 | 
			
		||||
  // time slot: t_slot: min=60µs, max=120µs
 | 
			
		||||
  // recovery time: t_rec: min=1µs
 | 
			
		||||
  // ds18b20 appears to read the bus after roughly 14µs
 | 
			
		||||
  uint32_t delay0 = bit ? 6 : 60;
 | 
			
		||||
  uint32_t delay1 = bit ? 59 : 5;
 | 
			
		||||
 | 
			
		||||
  // delay A/C
 | 
			
		||||
  delayMicroseconds(delay0);
 | 
			
		||||
  // release bus
 | 
			
		||||
  pin_.digital_write(true);
 | 
			
		||||
  // delay B/D
 | 
			
		||||
  delayMicroseconds(delay1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HOT IRAM_ATTR GPIOOneWireBus::read_bit_() {
 | 
			
		||||
  // drive bus low
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
 | 
			
		||||
  // note: for reading we'll need very accurate timing, as the
 | 
			
		||||
  // timing for the digital_read() is tight; according to the datasheet,
 | 
			
		||||
  // we should read at the end of 16µs starting from the bus low
 | 
			
		||||
  // typically, the ds18b20 pulls the line high after 11µs for a logical 1
 | 
			
		||||
  // and 29µs for a logical 0
 | 
			
		||||
 | 
			
		||||
  uint32_t start = micros();
 | 
			
		||||
  // datasheet says >1µs
 | 
			
		||||
  delayMicroseconds(2);
 | 
			
		||||
 | 
			
		||||
  // release bus, delay E
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
 | 
			
		||||
  // measure from start value directly, to get best accurate timing no matter
 | 
			
		||||
  // how long pin_mode/delayMicroseconds took
 | 
			
		||||
  delayMicroseconds(12 - (micros() - start));
 | 
			
		||||
 | 
			
		||||
  // sample bus to read bit from peer
 | 
			
		||||
  bool r = pin_.digital_read();
 | 
			
		||||
 | 
			
		||||
  // read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
 | 
			
		||||
  uint32_t now = micros();
 | 
			
		||||
  if (now - start < 60)
 | 
			
		||||
    delayMicroseconds(60 - (now - start));
 | 
			
		||||
 | 
			
		||||
  return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR GPIOOneWireBus::write8(uint8_t val) {
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    this->write_bit_(bool((1u << i) & val));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR GPIOOneWireBus::write64(uint64_t val) {
 | 
			
		||||
  for (uint8_t i = 0; i < 64; i++) {
 | 
			
		||||
    this->write_bit_(bool((1ULL << i) & val));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t IRAM_ATTR GPIOOneWireBus::read8() {
 | 
			
		||||
  uint8_t ret = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    ret |= (uint8_t(this->read_bit_()) << i);
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64_t IRAM_ATTR GPIOOneWireBus::read64() {
 | 
			
		||||
  uint64_t ret = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    ret |= (uint64_t(this->read_bit_()) << i);
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GPIOOneWireBus::reset_search() {
 | 
			
		||||
  this->last_discrepancy_ = 0;
 | 
			
		||||
  this->last_device_flag_ = false;
 | 
			
		||||
  this->address_ = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64_t IRAM_ATTR GPIOOneWireBus::search_int() {
 | 
			
		||||
  if (this->last_device_flag_)
 | 
			
		||||
    return 0u;
 | 
			
		||||
 | 
			
		||||
  uint8_t last_zero = 0;
 | 
			
		||||
  uint64_t bit_mask = 1;
 | 
			
		||||
  uint64_t address = this->address_;
 | 
			
		||||
 | 
			
		||||
  // Initiate search
 | 
			
		||||
  for (int bit_number = 1; bit_number <= 64; bit_number++, bit_mask <<= 1) {
 | 
			
		||||
    // read bit
 | 
			
		||||
    bool id_bit = this->read_bit_();
 | 
			
		||||
    // read its complement
 | 
			
		||||
    bool cmp_id_bit = this->read_bit_();
 | 
			
		||||
 | 
			
		||||
    if (id_bit && cmp_id_bit) {
 | 
			
		||||
      // No devices participating in search
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool branch;
 | 
			
		||||
 | 
			
		||||
    if (id_bit != cmp_id_bit) {
 | 
			
		||||
      // only chose one branch, the other one doesn't have any devices.
 | 
			
		||||
      branch = id_bit;
 | 
			
		||||
    } else {
 | 
			
		||||
      // there are devices with both 0s and 1s at this bit
 | 
			
		||||
      if (bit_number < this->last_discrepancy_) {
 | 
			
		||||
        branch = (address & bit_mask) > 0;
 | 
			
		||||
      } else {
 | 
			
		||||
        branch = bit_number == this->last_discrepancy_;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!branch) {
 | 
			
		||||
        last_zero = bit_number;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (branch) {
 | 
			
		||||
      address |= bit_mask;
 | 
			
		||||
    } else {
 | 
			
		||||
      address &= ~bit_mask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // choose/announce branch
 | 
			
		||||
    this->write_bit_(branch);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->last_discrepancy_ = last_zero;
 | 
			
		||||
  if (this->last_discrepancy_ == 0) {
 | 
			
		||||
    // we're at root and have no choices left, so this was the last one.
 | 
			
		||||
    this->last_device_flag_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->address_ = address;
 | 
			
		||||
  return address;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace gpio
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										41
									
								
								esphome/components/gpio/one_wire/gpio_one_wire.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								esphome/components/gpio/one_wire/gpio_one_wire.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/components/one_wire/one_wire.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace gpio {
 | 
			
		||||
 | 
			
		||||
class GPIOOneWireBus : public one_wire::OneWireBus, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::BUS; }
 | 
			
		||||
 | 
			
		||||
  void set_pin(InternalGPIOPin *pin) {
 | 
			
		||||
    this->t_pin_ = pin;
 | 
			
		||||
    this->pin_ = pin->to_isr();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool reset() override;
 | 
			
		||||
  void write8(uint8_t val) override;
 | 
			
		||||
  void write64(uint64_t val) override;
 | 
			
		||||
  uint8_t read8() override;
 | 
			
		||||
  uint64_t read64() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  InternalGPIOPin *t_pin_;
 | 
			
		||||
  ISRInternalGPIOPin pin_;
 | 
			
		||||
  uint8_t last_discrepancy_{0};
 | 
			
		||||
  bool last_device_flag_{false};
 | 
			
		||||
  uint64_t address_;
 | 
			
		||||
 | 
			
		||||
  void reset_search() override;
 | 
			
		||||
  uint64_t search_int() override;
 | 
			
		||||
  void write_bit_(bool bit);
 | 
			
		||||
  bool read_bit_();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace gpio
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -56,7 +56,7 @@ void HE60rCover::endstop_reached_(CoverOperation operation) {
 | 
			
		||||
    this->position = new_position;
 | 
			
		||||
    this->current_operation = COVER_OPERATION_IDLE;
 | 
			
		||||
    if (this->last_command_ == operation) {
 | 
			
		||||
      float dur = (now - this->start_dir_time_) / 1e3f;
 | 
			
		||||
      float dur = (float) (now - this->start_dir_time_) / 1e3f;
 | 
			
		||||
      ESP_LOGD(TAG, "'%s' - %s endstop reached. Took %.1fs.", this->name_.c_str(),
 | 
			
		||||
               operation == COVER_OPERATION_OPENING ? "Open" : "Close", dur);
 | 
			
		||||
    }
 | 
			
		||||
@@ -69,7 +69,6 @@ void HE60rCover::set_current_operation_(cover::CoverOperation operation) {
 | 
			
		||||
    this->current_operation = operation;
 | 
			
		||||
    if (operation != COVER_OPERATION_IDLE)
 | 
			
		||||
      this->last_recompute_time_ = millis();
 | 
			
		||||
    this->publish_state();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -129,7 +128,7 @@ void HE60rCover::update_() {
 | 
			
		||||
  if (this->toggles_needed_ != 0) {
 | 
			
		||||
    if ((this->counter_++ & 0x3) == 0) {
 | 
			
		||||
      this->toggles_needed_--;
 | 
			
		||||
      ESP_LOGD(TAG, "Writing byte 0x30, still needed=%" PRIu32, this->toggles_needed_);
 | 
			
		||||
      ESP_LOGD(TAG, "Writing byte 0x30, still needed=%u", this->toggles_needed_);
 | 
			
		||||
      this->write_byte(TOGGLE_BYTE);
 | 
			
		||||
    } else {
 | 
			
		||||
      this->write_byte(QUERY_BYTE);
 | 
			
		||||
@@ -235,33 +234,30 @@ void HE60rCover::recompute_position_() {
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  const uint32_t now = millis();
 | 
			
		||||
  float dir;
 | 
			
		||||
  float action_dur;
 | 
			
		||||
 | 
			
		||||
  if (now > this->last_recompute_time_) {
 | 
			
		||||
    auto diff = (unsigned) (now - last_recompute_time_);
 | 
			
		||||
    float delta;
 | 
			
		||||
    switch (this->current_operation) {
 | 
			
		||||
      case COVER_OPERATION_OPENING:
 | 
			
		||||
      dir = 1.0f;
 | 
			
		||||
      action_dur = this->open_duration_;
 | 
			
		||||
        delta = (float) diff / (float) this->open_duration_;
 | 
			
		||||
        break;
 | 
			
		||||
      case COVER_OPERATION_CLOSING:
 | 
			
		||||
      dir = -1.0f;
 | 
			
		||||
      action_dur = this->close_duration_;
 | 
			
		||||
        delta = -(float) diff / (float) this->close_duration_;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  if (now > this->last_recompute_time_) {
 | 
			
		||||
    auto diff = now - last_recompute_time_;
 | 
			
		||||
    auto delta = dir * diff / action_dur;
 | 
			
		||||
    // make sure our guesstimate never reaches full open or close.
 | 
			
		||||
    this->position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f);
 | 
			
		||||
    ESP_LOGD(TAG, "Recompute %dms, dir=%f, action_dur=%f, delta=%f, pos=%f", (int) diff, dir, action_dur, delta,
 | 
			
		||||
             this->position);
 | 
			
		||||
    auto new_position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f);
 | 
			
		||||
    ESP_LOGD(TAG, "Recompute %ums, dir=%u, delta=%f, pos=%f", diff, this->current_operation, delta, new_position);
 | 
			
		||||
    this->last_recompute_time_ = now;
 | 
			
		||||
    if (this->position != new_position) {
 | 
			
		||||
      this->position = new_position;
 | 
			
		||||
      this->publish_state();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace he60r
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -25,15 +25,14 @@ class HE60rCover : public cover::Cover, public Component, public uart::UARTDevic
 | 
			
		||||
  void control(const cover::CoverCall &call) override;
 | 
			
		||||
  bool is_at_target_() const;
 | 
			
		||||
  void start_direction_(cover::CoverOperation dir);
 | 
			
		||||
  void update_operation_(cover::CoverOperation dir);
 | 
			
		||||
  void endstop_reached_(cover::CoverOperation operation);
 | 
			
		||||
  void recompute_position_();
 | 
			
		||||
  void set_current_operation_(cover::CoverOperation operation);
 | 
			
		||||
  void process_rx_(uint8_t data);
 | 
			
		||||
 | 
			
		||||
  uint32_t open_duration_{0};
 | 
			
		||||
  uint32_t close_duration_{0};
 | 
			
		||||
  uint32_t toggles_needed_{0};
 | 
			
		||||
  unsigned open_duration_{0};
 | 
			
		||||
  unsigned close_duration_{0};
 | 
			
		||||
  unsigned toggles_needed_{0};
 | 
			
		||||
  cover::CoverOperation next_direction_{cover::COVER_OPERATION_IDLE};
 | 
			
		||||
  cover::CoverOperation last_command_{cover::COVER_OPERATION_IDLE};
 | 
			
		||||
  uint32_t last_recompute_time_{0};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
import urllib.parse as urlparse
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    __version__,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_TIMEOUT,
 | 
			
		||||
    CONF_METHOD,
 | 
			
		||||
@@ -12,67 +11,91 @@ from esphome.const import (
 | 
			
		||||
    CONF_ESP8266_DISABLE_SSL_SUPPORT,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import Lambda, CORE
 | 
			
		||||
from esphome.components import esp32
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["network"]
 | 
			
		||||
AUTO_LOAD = ["json"]
 | 
			
		||||
 | 
			
		||||
http_request_ns = cg.esphome_ns.namespace("http_request")
 | 
			
		||||
HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component)
 | 
			
		||||
HttpRequestArduino = http_request_ns.class_("HttpRequestArduino", HttpRequestComponent)
 | 
			
		||||
HttpRequestIDF = http_request_ns.class_("HttpRequestIDF", HttpRequestComponent)
 | 
			
		||||
 | 
			
		||||
HttpContainer = http_request_ns.class_("HttpContainer")
 | 
			
		||||
 | 
			
		||||
HttpRequestSendAction = http_request_ns.class_(
 | 
			
		||||
    "HttpRequestSendAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
HttpRequestResponseTrigger = http_request_ns.class_(
 | 
			
		||||
    "HttpRequestResponseTrigger", automation.Trigger
 | 
			
		||||
    "HttpRequestResponseTrigger",
 | 
			
		||||
    automation.Trigger.template(
 | 
			
		||||
        cg.std_shared_ptr.template(HttpContainer), cg.std_string
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_HEADERS = "headers"
 | 
			
		||||
CONF_HTTP_REQUEST_ID = "http_request_id"
 | 
			
		||||
 | 
			
		||||
CONF_USERAGENT = "useragent"
 | 
			
		||||
CONF_BODY = "body"
 | 
			
		||||
CONF_JSON = "json"
 | 
			
		||||
CONF_VERIFY_SSL = "verify_ssl"
 | 
			
		||||
CONF_ON_RESPONSE = "on_response"
 | 
			
		||||
CONF_FOLLOW_REDIRECTS = "follow_redirects"
 | 
			
		||||
CONF_REDIRECT_LIMIT = "redirect_limit"
 | 
			
		||||
CONF_WATCHDOG_TIMEOUT = "watchdog_timeout"
 | 
			
		||||
 | 
			
		||||
CONF_MAX_RESPONSE_BUFFER_SIZE = "max_response_buffer_size"
 | 
			
		||||
CONF_ON_RESPONSE = "on_response"
 | 
			
		||||
CONF_HEADERS = "headers"
 | 
			
		||||
CONF_BODY = "body"
 | 
			
		||||
CONF_JSON = "json"
 | 
			
		||||
CONF_CAPTURE_RESPONSE = "capture_response"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_url(value):
 | 
			
		||||
    value = cv.string(value)
 | 
			
		||||
    try:
 | 
			
		||||
        parsed = list(urlparse.urlparse(value))
 | 
			
		||||
    except Exception as err:
 | 
			
		||||
        raise cv.Invalid("Invalid URL") from err
 | 
			
		||||
 | 
			
		||||
    if not parsed[0] or not parsed[1]:
 | 
			
		||||
        raise cv.Invalid("URL must have a URL scheme and host")
 | 
			
		||||
 | 
			
		||||
    if parsed[0] not in ["http", "https"]:
 | 
			
		||||
        raise cv.Invalid("Scheme must be http or https")
 | 
			
		||||
 | 
			
		||||
    if not parsed[2]:
 | 
			
		||||
        parsed[2] = "/"
 | 
			
		||||
 | 
			
		||||
    return urlparse.urlunparse(parsed)
 | 
			
		||||
    value = cv.url(value)
 | 
			
		||||
    if value.startswith("http://") or value.startswith("https://"):
 | 
			
		||||
        return value
 | 
			
		||||
    raise cv.Invalid("URL must start with 'http://' or 'https://'")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_secure_url(config):
 | 
			
		||||
    url_ = config[CONF_URL]
 | 
			
		||||
def validate_ssl_verification(config):
 | 
			
		||||
    error_message = ""
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        if not CORE.using_esp_idf and config[CONF_VERIFY_SSL]:
 | 
			
		||||
            error_message = "ESPHome supports certificate verification only via ESP-IDF"
 | 
			
		||||
 | 
			
		||||
    if CORE.is_rp2040 and config[CONF_VERIFY_SSL]:
 | 
			
		||||
        error_message = "ESPHome does not support certificate verification on RP2040"
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        config.get(CONF_VERIFY_SSL)
 | 
			
		||||
        and not isinstance(url_, Lambda)
 | 
			
		||||
        and url_.lower().startswith("https:")
 | 
			
		||||
        CORE.is_esp8266
 | 
			
		||||
        and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]
 | 
			
		||||
        and config[CONF_VERIFY_SSL]
 | 
			
		||||
    ):
 | 
			
		||||
        error_message = "ESPHome does not support certificate verification on ESP8266"
 | 
			
		||||
 | 
			
		||||
    if len(error_message) > 0:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "Currently ESPHome doesn't support SSL verification. "
 | 
			
		||||
            "Set 'verify_ssl: false' to make insecure HTTPS requests."
 | 
			
		||||
            f"{error_message}. Set '{CONF_VERIFY_SSL}: false' to skip certificate validation and allow less secure HTTPS connections."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _declare_request_class(value):
 | 
			
		||||
    if CORE.using_esp_idf:
 | 
			
		||||
        return cv.declare_id(HttpRequestIDF)(value)
 | 
			
		||||
    if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040:
 | 
			
		||||
        return cv.declare_id(HttpRequestArduino)(value)
 | 
			
		||||
    return NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(HttpRequestComponent),
 | 
			
		||||
            cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string,
 | 
			
		||||
            cv.GenerateID(): _declare_request_class,
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_USERAGENT, f"ESPHome/{__version__} (https://esphome.io)"
 | 
			
		||||
            ): cv.string,
 | 
			
		||||
            cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_,
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
@@ -81,12 +104,21 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All(
 | 
			
		||||
                cv.only_on_esp8266, cv.boolean
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_WATCHDOG_TIMEOUT): cv.All(
 | 
			
		||||
                cv.Any(cv.only_on_esp32, cv.only_on_rp2040),
 | 
			
		||||
                cv.positive_not_null_time_period,
 | 
			
		||||
                cv.positive_time_period_milliseconds,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.require_framework_version(
 | 
			
		||||
        esp8266_arduino=cv.Version(2, 5, 1),
 | 
			
		||||
        esp32_arduino=cv.Version(0, 0, 0),
 | 
			
		||||
        esp_idf=cv.Version(0, 0, 0),
 | 
			
		||||
        rp2040_arduino=cv.Version(0, 0, 0),
 | 
			
		||||
    ),
 | 
			
		||||
    validate_ssl_verification,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -100,11 +132,30 @@ async def to_code(config):
 | 
			
		||||
    if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]:
 | 
			
		||||
        cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS")
 | 
			
		||||
 | 
			
		||||
    if timeout_ms := config.get(CONF_WATCHDOG_TIMEOUT):
 | 
			
		||||
        cg.add(var.set_watchdog_timeout(timeout_ms))
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        if CORE.using_esp_idf:
 | 
			
		||||
            esp32.add_idf_sdkconfig_option(
 | 
			
		||||
                "CONFIG_MBEDTLS_CERTIFICATE_BUNDLE",
 | 
			
		||||
                config.get(CONF_VERIFY_SSL),
 | 
			
		||||
            )
 | 
			
		||||
            esp32.add_idf_sdkconfig_option(
 | 
			
		||||
                "CONFIG_ESP_TLS_INSECURE",
 | 
			
		||||
                not config.get(CONF_VERIFY_SSL),
 | 
			
		||||
            )
 | 
			
		||||
            esp32.add_idf_sdkconfig_option(
 | 
			
		||||
                "CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY",
 | 
			
		||||
                not config.get(CONF_VERIFY_SSL),
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            cg.add_library("WiFiClientSecure", None)
 | 
			
		||||
            cg.add_library("HTTPClient", None)
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        cg.add_library("ESP8266HTTPClient", None)
 | 
			
		||||
    if CORE.is_rp2040 and CORE.using_arduino:
 | 
			
		||||
        cg.add_library("HTTPClient", None)
 | 
			
		||||
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
@@ -116,12 +167,16 @@ HTTP_REQUEST_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
        cv.Optional(CONF_HEADERS): cv.All(
 | 
			
		||||
            cv.Schema({cv.string: cv.templatable(cv.string)})
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_VERIFY_SSL): cv.invalid(
 | 
			
		||||
            f"{CONF_VERIFY_SSL} has moved to the base component configuration."
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_CAPTURE_RESPONSE, default=False): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_ON_RESPONSE): automation.validate_automation(
 | 
			
		||||
            {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(HttpRequestResponseTrigger)}
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_MAX_RESPONSE_BUFFER_SIZE, default="1kB"): cv.validate_bytes,
 | 
			
		||||
    }
 | 
			
		||||
).add_extra(validate_secure_url)
 | 
			
		||||
)
 | 
			
		||||
HTTP_REQUEST_GET_ACTION_SCHEMA = automation.maybe_conf(
 | 
			
		||||
    CONF_URL,
 | 
			
		||||
    HTTP_REQUEST_ACTION_SCHEMA.extend(
 | 
			
		||||
@@ -173,6 +228,9 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
 | 
			
		||||
    cg.add(var.set_url(template_))
 | 
			
		||||
    cg.add(var.set_method(config[CONF_METHOD]))
 | 
			
		||||
    cg.add(var.set_capture_response(config[CONF_CAPTURE_RESPONSE]))
 | 
			
		||||
    cg.add(var.set_max_response_buffer_size(config[CONF_MAX_RESPONSE_BUFFER_SIZE]))
 | 
			
		||||
 | 
			
		||||
    if CONF_BODY in config:
 | 
			
		||||
        template_ = await cg.templatable(config[CONF_BODY], args, cg.std_string)
 | 
			
		||||
        cg.add(var.set_body(template_))
 | 
			
		||||
@@ -196,7 +254,12 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
 | 
			
		||||
        cg.add(var.register_response_trigger(trigger))
 | 
			
		||||
        await automation.build_automation(
 | 
			
		||||
            trigger, [(int, "status_code"), (cg.uint32, "duration_ms")], conf
 | 
			
		||||
            trigger,
 | 
			
		||||
            [
 | 
			
		||||
                (cg.std_shared_ptr.template(HttpContainer), "response"),
 | 
			
		||||
                (cg.std_string, "body"),
 | 
			
		||||
            ],
 | 
			
		||||
            conf,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include "http_request.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
@@ -14,131 +13,12 @@ void HttpRequestComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "HTTP Request:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Timeout: %ums", this->timeout_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  User-Agent: %s", this->useragent_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Follow Redirects: %d", this->follow_redirects_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Follow redirects: %s", YESNO(this->follow_redirects_));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Redirect limit: %d", this->redirect_limit_);
 | 
			
		||||
  if (this->watchdog_timeout_ > 0) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Watchdog Timeout: %" PRIu32 "ms", this->watchdog_timeout_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
void HttpRequestComponent::set_url(std::string url) {
 | 
			
		||||
  this->url_ = std::move(url);
 | 
			
		||||
  this->secure_ = this->url_.compare(0, 6, "https:") == 0;
 | 
			
		||||
 | 
			
		||||
  if (!this->last_url_.empty() && this->url_ != this->last_url_) {
 | 
			
		||||
    // Close connection if url has been changed
 | 
			
		||||
    this->client_.setReuse(false);
 | 
			
		||||
    this->client_.end();
 | 
			
		||||
  }
 | 
			
		||||
  this->client_.setReuse(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HttpRequestComponent::send(const std::vector<HttpRequestResponseTrigger *> &response_triggers) {
 | 
			
		||||
  if (!network::is_connected()) {
 | 
			
		||||
    this->client_.end();
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool begin_status = false;
 | 
			
		||||
  const String url = this->url_.c_str();
 | 
			
		||||
#if defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0))
 | 
			
		||||
#if defined(USE_ESP32) || USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
 | 
			
		||||
  if (this->follow_redirects_) {
 | 
			
		||||
    this->client_.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->client_.setFollowRedirects(HTTPC_DISABLE_FOLLOW_REDIRECTS);
 | 
			
		||||
  }
 | 
			
		||||
#else
 | 
			
		||||
  this->client_.setFollowRedirects(this->follow_redirects_);
 | 
			
		||||
#endif
 | 
			
		||||
  this->client_.setRedirectLimit(this->redirect_limit_);
 | 
			
		||||
#endif
 | 
			
		||||
#if defined(USE_ESP32)
 | 
			
		||||
  begin_status = this->client_.begin(url);
 | 
			
		||||
#elif defined(USE_ESP8266)
 | 
			
		||||
  begin_status = this->client_.begin(*this->get_wifi_client_(), url);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (!begin_status) {
 | 
			
		||||
    this->client_.end();
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed at the begin phase. Please check the configuration");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->client_.setTimeout(this->timeout_);
 | 
			
		||||
#if defined(USE_ESP32)
 | 
			
		||||
  this->client_.setConnectTimeout(this->timeout_);
 | 
			
		||||
#endif
 | 
			
		||||
  if (this->useragent_ != nullptr) {
 | 
			
		||||
    this->client_.setUserAgent(this->useragent_);
 | 
			
		||||
  }
 | 
			
		||||
  for (const auto &header : this->headers_) {
 | 
			
		||||
    this->client_.addHeader(header.name, header.value, false, true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint32_t start_time = millis();
 | 
			
		||||
  int http_code = this->client_.sendRequest(this->method_, this->body_.c_str());
 | 
			
		||||
  uint32_t duration = millis() - start_time;
 | 
			
		||||
  for (auto *trigger : response_triggers)
 | 
			
		||||
    trigger->process(http_code, duration);
 | 
			
		||||
 | 
			
		||||
  if (http_code < 0) {
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s; Duration: %u ms", this->url_.c_str(),
 | 
			
		||||
             HTTPClient::errorToString(http_code).c_str(), duration);
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (http_code < 200 || http_code >= 300) {
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Code: %d; Duration: %u ms", this->url_.c_str(), http_code, duration);
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
  ESP_LOGD(TAG, "HTTP Request completed; URL: %s; Code: %d; Duration: %u ms", this->url_.c_str(), http_code, duration);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
std::shared_ptr<WiFiClient> HttpRequestComponent::get_wifi_client_() {
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
  if (this->secure_) {
 | 
			
		||||
    if (this->wifi_client_secure_ == nullptr) {
 | 
			
		||||
      this->wifi_client_secure_ = std::make_shared<BearSSL::WiFiClientSecure>();
 | 
			
		||||
      this->wifi_client_secure_->setInsecure();
 | 
			
		||||
      this->wifi_client_secure_->setBufferSizes(512, 512);
 | 
			
		||||
    }
 | 
			
		||||
    return this->wifi_client_secure_;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (this->wifi_client_ == nullptr) {
 | 
			
		||||
    this->wifi_client_ = std::make_shared<WiFiClient>();
 | 
			
		||||
  }
 | 
			
		||||
  return this->wifi_client_;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void HttpRequestComponent::close() {
 | 
			
		||||
  this->last_url_ = this->url_;
 | 
			
		||||
  this->client_.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const char *HttpRequestComponent::get_string() {
 | 
			
		||||
#if defined(ESP32)
 | 
			
		||||
  // The static variable is here because HTTPClient::getString() returns a String on ESP32,
 | 
			
		||||
  // and we need something to keep a buffer alive.
 | 
			
		||||
  static String str;
 | 
			
		||||
#else
 | 
			
		||||
  // However on ESP8266, HTTPClient::getString() returns a String& to a member variable.
 | 
			
		||||
  // Leaving this the default so that any new platform either doesn't copy, or encounters a compilation error.
 | 
			
		||||
  auto &
 | 
			
		||||
#endif
 | 
			
		||||
  str = this->client_.getString();
 | 
			
		||||
  return str.c_str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,18 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/json/json_util.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#include <list>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include <HTTPClient.h>
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
#include <ESP8266HTTPClient.h>
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
#include <WiFiClientSecure.h>
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
#include "esphome/components/json/json_util.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
@@ -31,9 +22,32 @@ struct Header {
 | 
			
		||||
  const char *value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class HttpRequestResponseTrigger : public Trigger<int32_t, uint32_t> {
 | 
			
		||||
class HttpRequestComponent;
 | 
			
		||||
 | 
			
		||||
class HttpContainer : public Parented<HttpRequestComponent> {
 | 
			
		||||
 public:
 | 
			
		||||
  void process(int32_t status_code, uint32_t duration_ms) { this->trigger(status_code, duration_ms); }
 | 
			
		||||
  virtual ~HttpContainer() = default;
 | 
			
		||||
  size_t content_length;
 | 
			
		||||
  int status_code;
 | 
			
		||||
  uint32_t duration_ms;
 | 
			
		||||
 | 
			
		||||
  virtual int read(uint8_t *buf, size_t max_len) = 0;
 | 
			
		||||
  virtual void end() = 0;
 | 
			
		||||
 | 
			
		||||
  void set_secure(bool secure) { this->secure_ = secure; }
 | 
			
		||||
 | 
			
		||||
  size_t get_bytes_read() const { return this->bytes_read_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  size_t bytes_read_{0};
 | 
			
		||||
  bool secure_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string> {
 | 
			
		||||
 public:
 | 
			
		||||
  void process(std::shared_ptr<HttpContainer> container, std::string response_body) {
 | 
			
		||||
    this->trigger(std::move(container), std::move(response_body));
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class HttpRequestComponent : public Component {
 | 
			
		||||
@@ -41,37 +55,33 @@ class HttpRequestComponent : public Component {
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
 | 
			
		||||
  void set_url(std::string url);
 | 
			
		||||
  void set_method(const char *method) { this->method_ = method; }
 | 
			
		||||
  void set_useragent(const char *useragent) { this->useragent_ = useragent; }
 | 
			
		||||
  void set_timeout(uint16_t timeout) { this->timeout_ = timeout; }
 | 
			
		||||
  void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; }
 | 
			
		||||
  uint32_t get_watchdog_timeout() const { return this->watchdog_timeout_; }
 | 
			
		||||
  void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
 | 
			
		||||
  void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; }
 | 
			
		||||
  void set_body(const std::string &body) { this->body_ = body; }
 | 
			
		||||
  void set_headers(std::list<Header> headers) { this->headers_ = std::move(headers); }
 | 
			
		||||
  void send(const std::vector<HttpRequestResponseTrigger *> &response_triggers);
 | 
			
		||||
  void close();
 | 
			
		||||
  const char *get_string();
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<HttpContainer> get(std::string url) { return this->start(std::move(url), "GET", "", {}); }
 | 
			
		||||
  std::shared_ptr<HttpContainer> get(std::string url, std::list<Header> headers) {
 | 
			
		||||
    return this->start(std::move(url), "GET", "", std::move(headers));
 | 
			
		||||
  }
 | 
			
		||||
  std::shared_ptr<HttpContainer> post(std::string url, std::string body) {
 | 
			
		||||
    return this->start(std::move(url), "POST", std::move(body), {});
 | 
			
		||||
  }
 | 
			
		||||
  std::shared_ptr<HttpContainer> post(std::string url, std::string body, std::list<Header> headers) {
 | 
			
		||||
    return this->start(std::move(url), "POST", std::move(body), std::move(headers));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
 | 
			
		||||
                                               std::list<Header> headers) = 0;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  HTTPClient client_{};
 | 
			
		||||
  std::string url_;
 | 
			
		||||
  std::string last_url_;
 | 
			
		||||
  const char *method_;
 | 
			
		||||
  const char *useragent_{nullptr};
 | 
			
		||||
  bool secure_;
 | 
			
		||||
  bool follow_redirects_;
 | 
			
		||||
  uint16_t redirect_limit_;
 | 
			
		||||
  uint16_t timeout_{5000};
 | 
			
		||||
  std::string body_;
 | 
			
		||||
  std::list<Header> headers_;
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
  std::shared_ptr<WiFiClient> wifi_client_;
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
  std::shared_ptr<BearSSL::WiFiClientSecure> wifi_client_secure_;
 | 
			
		||||
#endif
 | 
			
		||||
  std::shared_ptr<WiFiClient> get_wifi_client_();
 | 
			
		||||
#endif
 | 
			
		||||
  uint32_t watchdog_timeout_{0};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
@@ -80,6 +90,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, url)
 | 
			
		||||
  TEMPLATABLE_VALUE(const char *, method)
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, body)
 | 
			
		||||
  TEMPLATABLE_VALUE(bool, capture_response)
 | 
			
		||||
 | 
			
		||||
  void add_header(const char *key, TemplatableValue<const char *, Ts...> value) { this->headers_.insert({key, value}); }
 | 
			
		||||
 | 
			
		||||
@@ -89,19 +100,22 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
 | 
			
		||||
  void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); }
 | 
			
		||||
 | 
			
		||||
  void set_max_response_buffer_size(size_t max_response_buffer_size) {
 | 
			
		||||
    this->max_response_buffer_size_ = max_response_buffer_size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    this->parent_->set_url(this->url_.value(x...));
 | 
			
		||||
    this->parent_->set_method(this->method_.value(x...));
 | 
			
		||||
    std::string body;
 | 
			
		||||
    if (this->body_.has_value()) {
 | 
			
		||||
      this->parent_->set_body(this->body_.value(x...));
 | 
			
		||||
      body = this->body_.value(x...);
 | 
			
		||||
    }
 | 
			
		||||
    if (!this->json_.empty()) {
 | 
			
		||||
      auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_, this, x..., std::placeholders::_1);
 | 
			
		||||
      this->parent_->set_body(json::build_json(f));
 | 
			
		||||
      body = json::build_json(f);
 | 
			
		||||
    }
 | 
			
		||||
    if (this->json_func_ != nullptr) {
 | 
			
		||||
      auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_func_, this, x..., std::placeholders::_1);
 | 
			
		||||
      this->parent_->set_body(json::build_json(f));
 | 
			
		||||
      body = json::build_json(f);
 | 
			
		||||
    }
 | 
			
		||||
    std::list<Header> headers;
 | 
			
		||||
    for (const auto &item : this->headers_) {
 | 
			
		||||
@@ -111,10 +125,37 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
      header.value = val.value(x...);
 | 
			
		||||
      headers.push_back(header);
 | 
			
		||||
    }
 | 
			
		||||
    this->parent_->set_headers(headers);
 | 
			
		||||
    this->parent_->send(this->response_triggers_);
 | 
			
		||||
    this->parent_->close();
 | 
			
		||||
    this->parent_->set_body("");
 | 
			
		||||
 | 
			
		||||
    auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, headers);
 | 
			
		||||
 | 
			
		||||
    if (container == nullptr) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    size_t content_length = container->content_length;
 | 
			
		||||
    size_t max_length = std::min(content_length, this->max_response_buffer_size_);
 | 
			
		||||
 | 
			
		||||
    std::string response_body;
 | 
			
		||||
    if (this->capture_response_.value(x...)) {
 | 
			
		||||
      ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
 | 
			
		||||
      uint8_t *buf = allocator.allocate(max_length);
 | 
			
		||||
      if (buf != nullptr) {
 | 
			
		||||
        size_t read_index = 0;
 | 
			
		||||
        while (container->get_bytes_read() < max_length) {
 | 
			
		||||
          int read = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512));
 | 
			
		||||
          App.feed_wdt();
 | 
			
		||||
          yield();
 | 
			
		||||
          read_index += read;
 | 
			
		||||
        }
 | 
			
		||||
        response_body.reserve(read_index);
 | 
			
		||||
        response_body.assign((char *) buf, read_index);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (auto *trigger : this->response_triggers_) {
 | 
			
		||||
      trigger->process(container, response_body);
 | 
			
		||||
    }
 | 
			
		||||
    container->end();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
@@ -130,9 +171,9 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
  std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
 | 
			
		||||
  std::function<void(Ts..., JsonObject)> json_func_{nullptr};
 | 
			
		||||
  std::vector<HttpRequestResponseTrigger *> response_triggers_;
 | 
			
		||||
 | 
			
		||||
  size_t max_response_buffer_size_{SIZE_MAX};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										161
									
								
								esphome/components/http_request/http_request_arduino.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								esphome/components/http_request/http_request_arduino.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
			
		||||
#include "http_request_arduino.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include "watchdog.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "http_request.arduino";
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::string method, std::string body,
 | 
			
		||||
                                                         std::list<Header> headers) {
 | 
			
		||||
  if (!network::is_connected()) {
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<HttpContainerArduino> container = std::make_shared<HttpContainerArduino>();
 | 
			
		||||
  container->set_parent(this);
 | 
			
		||||
 | 
			
		||||
  const uint32_t start = millis();
 | 
			
		||||
 | 
			
		||||
  bool secure = url.find("https:") != std::string::npos;
 | 
			
		||||
  container->set_secure(secure);
 | 
			
		||||
 | 
			
		||||
  watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP8266)
 | 
			
		||||
  std::unique_ptr<WiFiClient> stream_ptr;
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
  if (secure) {
 | 
			
		||||
    ESP_LOGV(TAG, "ESP8266 HTTPS connection with WiFiClientSecure");
 | 
			
		||||
    stream_ptr = std::make_unique<WiFiClientSecure>();
 | 
			
		||||
    WiFiClientSecure *secure_client = static_cast<WiFiClientSecure *>(stream_ptr.get());
 | 
			
		||||
    secure_client->setBufferSizes(512, 512);
 | 
			
		||||
    secure_client->setInsecure();
 | 
			
		||||
  } else {
 | 
			
		||||
    stream_ptr = std::make_unique<WiFiClient>();
 | 
			
		||||
  }
 | 
			
		||||
#else
 | 
			
		||||
  ESP_LOGV(TAG, "ESP8266 HTTP connection with WiFiClient");
 | 
			
		||||
  if (secure) {
 | 
			
		||||
    ESP_LOGE(TAG, "Can't use HTTPS connection with esp8266_disable_ssl_support");
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
  stream_ptr = std::make_unique<WiFiClient>();
 | 
			
		||||
#endif  // USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
 | 
			
		||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0)  // && USE_ARDUINO_VERSION_CODE < VERSION_CODE(?, ?, ?)
 | 
			
		||||
  if (!secure) {
 | 
			
		||||
    ESP_LOGW(TAG, "Using HTTP on Arduino version >= 3.1 is **very** slow. Consider setting framework version to 3.0.2 "
 | 
			
		||||
                  "in your YAML, or use HTTPS");
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_ARDUINO_VERSION_CODE
 | 
			
		||||
 | 
			
		||||
  container->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
 | 
			
		||||
  bool status = container->client_.begin(*stream_ptr, url.c_str());
 | 
			
		||||
 | 
			
		||||
#elif defined(USE_RP2040)
 | 
			
		||||
  if (secure) {
 | 
			
		||||
    container->client_.setInsecure();
 | 
			
		||||
  }
 | 
			
		||||
  bool status = container->client_.begin(url.c_str());
 | 
			
		||||
#elif defined(USE_ESP32)
 | 
			
		||||
  bool status = container->client_.begin(url.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
 | 
			
		||||
  if (!status) {
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; URL: %s", url.c_str());
 | 
			
		||||
    container->end();
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  container->client_.setReuse(true);
 | 
			
		||||
  container->client_.setTimeout(this->timeout_);
 | 
			
		||||
#if defined(USE_ESP32)
 | 
			
		||||
  container->client_.setConnectTimeout(this->timeout_);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (this->useragent_ != nullptr) {
 | 
			
		||||
    container->client_.setUserAgent(this->useragent_);
 | 
			
		||||
  }
 | 
			
		||||
  for (const auto &header : headers) {
 | 
			
		||||
    container->client_.addHeader(header.name, header.value, false, true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // returned needed headers must be collected before the requests
 | 
			
		||||
  static const char *header_keys[] = {"Content-Length", "Content-Type"};
 | 
			
		||||
  static const size_t HEADER_COUNT = sizeof(header_keys) / sizeof(header_keys[0]);
 | 
			
		||||
  container->client_.collectHeaders(header_keys, HEADER_COUNT);
 | 
			
		||||
 | 
			
		||||
  container->status_code = container->client_.sendRequest(method.c_str(), body.c_str());
 | 
			
		||||
  if (container->status_code < 0) {
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s", url.c_str(),
 | 
			
		||||
             HTTPClient::errorToString(container->status_code).c_str());
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    container->end();
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (container->status_code < 200 || container->status_code >= 300) {
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code);
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    container->end();
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int content_length = container->client_.getSize();
 | 
			
		||||
  ESP_LOGD(TAG, "Content-Length: %d", content_length);
 | 
			
		||||
  container->content_length = (size_t) content_length;
 | 
			
		||||
  container->duration_ms = millis() - start;
 | 
			
		||||
 | 
			
		||||
  return container;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int HttpContainerArduino::read(uint8_t *buf, size_t max_len) {
 | 
			
		||||
  const uint32_t start = millis();
 | 
			
		||||
  watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
 | 
			
		||||
 | 
			
		||||
  WiFiClient *stream_ptr = this->client_.getStreamPtr();
 | 
			
		||||
  if (stream_ptr == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Stream pointer vanished!");
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int available_data = stream_ptr->available();
 | 
			
		||||
  int bufsize = std::min(max_len, std::min(this->content_length - this->bytes_read_, (size_t) available_data));
 | 
			
		||||
 | 
			
		||||
  if (bufsize == 0) {
 | 
			
		||||
    this->duration_ms += (millis() - start);
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
  int read_len = stream_ptr->readBytes(buf, bufsize);
 | 
			
		||||
  this->bytes_read_ += read_len;
 | 
			
		||||
 | 
			
		||||
  this->duration_ms += (millis() - start);
 | 
			
		||||
 | 
			
		||||
  return read_len;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HttpContainerArduino::end() {
 | 
			
		||||
  watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
 | 
			
		||||
  this->client_.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
							
								
								
									
										40
									
								
								esphome/components/http_request/http_request_arduino.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								esphome/components/http_request/http_request_arduino.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "http_request.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_RP2040)
 | 
			
		||||
#include <HTTPClient.h>
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
#include <ESP8266HTTPClient.h>
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
#include <WiFiClientSecure.h>
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
class HttpRequestArduino;
 | 
			
		||||
class HttpContainerArduino : public HttpContainer {
 | 
			
		||||
 public:
 | 
			
		||||
  int read(uint8_t *buf, size_t max_len) override;
 | 
			
		||||
  void end() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  friend class HttpRequestArduino;
 | 
			
		||||
  HTTPClient client_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class HttpRequestArduino : public HttpRequestComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
 | 
			
		||||
                                       std::list<Header> headers) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
							
								
								
									
										155
									
								
								esphome/components/http_request/http_request_idf.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								esphome/components/http_request/http_request_idf.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
			
		||||
#include "http_request_idf.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
 | 
			
		||||
#include "esp_crt_bundle.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "watchdog.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "http_request.idf";
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::string method, std::string body,
 | 
			
		||||
                                                     std::list<Header> headers) {
 | 
			
		||||
  if (!network::is_connected()) {
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  esp_http_client_method_t method_idf;
 | 
			
		||||
  if (method == "GET") {
 | 
			
		||||
    method_idf = HTTP_METHOD_GET;
 | 
			
		||||
  } else if (method == "POST") {
 | 
			
		||||
    method_idf = HTTP_METHOD_POST;
 | 
			
		||||
  } else if (method == "PUT") {
 | 
			
		||||
    method_idf = HTTP_METHOD_PUT;
 | 
			
		||||
  } else if (method == "DELETE") {
 | 
			
		||||
    method_idf = HTTP_METHOD_DELETE;
 | 
			
		||||
  } else if (method == "PATCH") {
 | 
			
		||||
    method_idf = HTTP_METHOD_PATCH;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP Request failed; Unsupported method");
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool secure = url.find("https:") != std::string::npos;
 | 
			
		||||
 | 
			
		||||
  esp_http_client_config_t config = {};
 | 
			
		||||
 | 
			
		||||
  config.url = url.c_str();
 | 
			
		||||
  config.method = method_idf;
 | 
			
		||||
  config.timeout_ms = this->timeout_;
 | 
			
		||||
  config.disable_auto_redirect = !this->follow_redirects_;
 | 
			
		||||
  config.max_redirection_count = this->redirect_limit_;
 | 
			
		||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
 | 
			
		||||
  if (secure) {
 | 
			
		||||
    config.crt_bundle_attach = esp_crt_bundle_attach;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (this->useragent_ != nullptr) {
 | 
			
		||||
    config.user_agent = this->useragent_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const uint32_t start = millis();
 | 
			
		||||
  watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
 | 
			
		||||
 | 
			
		||||
  esp_http_client_handle_t client = esp_http_client_init(&config);
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<HttpContainerIDF> container = std::make_shared<HttpContainerIDF>(client);
 | 
			
		||||
  container->set_parent(this);
 | 
			
		||||
 | 
			
		||||
  container->set_secure(secure);
 | 
			
		||||
 | 
			
		||||
  for (const auto &header : headers) {
 | 
			
		||||
    esp_http_client_set_header(client, header.name, header.value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int body_len = body.length();
 | 
			
		||||
 | 
			
		||||
  esp_err_t err = esp_http_client_open(client, body_len);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err));
 | 
			
		||||
    esp_http_client_cleanup(client);
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (body_len > 0) {
 | 
			
		||||
    int write_left = body_len;
 | 
			
		||||
    int write_index = 0;
 | 
			
		||||
    const char *buf = body.c_str();
 | 
			
		||||
    while (write_left > 0) {
 | 
			
		||||
      int written = esp_http_client_write(client, buf + write_index, write_left);
 | 
			
		||||
      if (written < 0) {
 | 
			
		||||
        err = ESP_FAIL;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      write_left -= written;
 | 
			
		||||
      write_index += written;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err));
 | 
			
		||||
    esp_http_client_cleanup(client);
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  container->content_length = esp_http_client_fetch_headers(client);
 | 
			
		||||
  const auto status_code = esp_http_client_get_status_code(client);
 | 
			
		||||
  container->status_code = status_code;
 | 
			
		||||
 | 
			
		||||
  if (status_code < 200 || status_code >= 300) {
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), status_code);
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    esp_http_client_cleanup(client);
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
  container->duration_ms = millis() - start;
 | 
			
		||||
  return container;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
 | 
			
		||||
  const uint32_t start = millis();
 | 
			
		||||
  watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
 | 
			
		||||
 | 
			
		||||
  int bufsize = std::min(max_len, this->content_length - this->bytes_read_);
 | 
			
		||||
 | 
			
		||||
  if (bufsize == 0) {
 | 
			
		||||
    this->duration_ms += (millis() - start);
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
  int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize);
 | 
			
		||||
  this->bytes_read_ += read_len;
 | 
			
		||||
 | 
			
		||||
  this->duration_ms += (millis() - start);
 | 
			
		||||
 | 
			
		||||
  return read_len;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HttpContainerIDF::end() {
 | 
			
		||||
  watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
 | 
			
		||||
 | 
			
		||||
  esp_http_client_close(this->client_);
 | 
			
		||||
  esp_http_client_cleanup(this->client_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
							
								
								
									
										34
									
								
								esphome/components/http_request/http_request_idf.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								esphome/components/http_request/http_request_idf.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "http_request.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#include <esp_event.h>
 | 
			
		||||
#include <esp_http_client.h>
 | 
			
		||||
#include <esp_netif.h>
 | 
			
		||||
#include <esp_tls.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
class HttpContainerIDF : public HttpContainer {
 | 
			
		||||
 public:
 | 
			
		||||
  HttpContainerIDF(esp_http_client_handle_t client) : client_(client) {}
 | 
			
		||||
  int read(uint8_t *buf, size_t max_len) override;
 | 
			
		||||
  void end() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  esp_http_client_handle_t client_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class HttpRequestIDF : public HttpRequestComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
 | 
			
		||||
                                       std::list<Header> headers) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
@@ -2,92 +2,35 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ESP8266_DISABLE_SSL_SUPPORT,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_PASSWORD,
 | 
			
		||||
    CONF_TIMEOUT,
 | 
			
		||||
    CONF_URL,
 | 
			
		||||
    CONF_USERNAME,
 | 
			
		||||
)
 | 
			
		||||
from esphome.components import esp32
 | 
			
		||||
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from .. import http_request_ns
 | 
			
		||||
from esphome.core import coroutine_with_priority
 | 
			
		||||
from .. import CONF_HTTP_REQUEST_ID, http_request_ns, HttpRequestComponent
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@oarcher"]
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["md5"]
 | 
			
		||||
DEPENDENCIES = ["network"]
 | 
			
		||||
DEPENDENCIES = ["network", "http_request"]
 | 
			
		||||
 | 
			
		||||
CONF_MD5 = "md5"
 | 
			
		||||
CONF_MD5_URL = "md5_url"
 | 
			
		||||
CONF_VERIFY_SSL = "verify_ssl"
 | 
			
		||||
CONF_WATCHDOG_TIMEOUT = "watchdog_timeout"
 | 
			
		||||
 | 
			
		||||
OtaHttpRequestComponent = http_request_ns.class_(
 | 
			
		||||
    "OtaHttpRequestComponent", OTAComponent
 | 
			
		||||
)
 | 
			
		||||
OtaHttpRequestComponentArduino = http_request_ns.class_(
 | 
			
		||||
    "OtaHttpRequestComponentArduino", OtaHttpRequestComponent
 | 
			
		||||
)
 | 
			
		||||
OtaHttpRequestComponentIDF = http_request_ns.class_(
 | 
			
		||||
    "OtaHttpRequestComponentIDF", OtaHttpRequestComponent
 | 
			
		||||
)
 | 
			
		||||
OtaHttpRequestComponentFlashAction = http_request_ns.class_(
 | 
			
		||||
    "OtaHttpRequestComponentFlashAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_ssl_verification(config):
 | 
			
		||||
    error_message = ""
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        if not CORE.using_esp_idf and config[CONF_VERIFY_SSL]:
 | 
			
		||||
            error_message = "ESPHome supports certificate verification only via ESP-IDF"
 | 
			
		||||
 | 
			
		||||
    if CORE.is_rp2040 and config[CONF_VERIFY_SSL]:
 | 
			
		||||
        error_message = "ESPHome does not support certificate verification in Arduino"
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        CORE.is_esp8266
 | 
			
		||||
        and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]
 | 
			
		||||
        and config[CONF_VERIFY_SSL]
 | 
			
		||||
    ):
 | 
			
		||||
        error_message = "ESPHome does not support certificate verification in Arduino"
 | 
			
		||||
 | 
			
		||||
    if len(error_message) > 0:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"{error_message}. Set '{CONF_VERIFY_SSL}: false' to skip certificate validation and allow less secure HTTPS connections."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _declare_request_class(value):
 | 
			
		||||
    if CORE.using_esp_idf:
 | 
			
		||||
        return cv.declare_id(OtaHttpRequestComponentIDF)(value)
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040:
 | 
			
		||||
        return cv.declare_id(OtaHttpRequestComponentArduino)(value)
 | 
			
		||||
    return NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): _declare_request_class,
 | 
			
		||||
            cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All(
 | 
			
		||||
                cv.only_on_esp8266, cv.boolean
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_TIMEOUT, default="5min"
 | 
			
		||||
            ): cv.positive_time_period_milliseconds,
 | 
			
		||||
            cv.Optional(CONF_WATCHDOG_TIMEOUT): cv.All(
 | 
			
		||||
                cv.Any(cv.only_on_esp32, cv.only_on_rp2040),
 | 
			
		||||
                cv.positive_not_null_time_period,
 | 
			
		||||
                cv.positive_time_period_milliseconds,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(OtaHttpRequestComponent),
 | 
			
		||||
            cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(BASE_OTA_SCHEMA)
 | 
			
		||||
@@ -98,7 +41,6 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
        esp_idf=cv.Version(0, 0, 0),
 | 
			
		||||
        rp2040_arduino=cv.Version(0, 0, 0),
 | 
			
		||||
    ),
 | 
			
		||||
    validate_ssl_verification,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -106,41 +48,8 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await ota_to_code(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_timeout(config[CONF_TIMEOUT]))
 | 
			
		||||
 | 
			
		||||
    if timeout_ms := config.get(CONF_WATCHDOG_TIMEOUT):
 | 
			
		||||
        cg.add_define(
 | 
			
		||||
            "USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT",
 | 
			
		||||
            timeout_ms,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]:
 | 
			
		||||
        cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS")
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        if CORE.using_esp_idf:
 | 
			
		||||
            esp32.add_idf_sdkconfig_option(
 | 
			
		||||
                "CONFIG_MBEDTLS_CERTIFICATE_BUNDLE",
 | 
			
		||||
                config.get(CONF_VERIFY_SSL),
 | 
			
		||||
            )
 | 
			
		||||
            esp32.add_idf_sdkconfig_option(
 | 
			
		||||
                "CONFIG_ESP_TLS_INSECURE",
 | 
			
		||||
                not config.get(CONF_VERIFY_SSL),
 | 
			
		||||
            )
 | 
			
		||||
            esp32.add_idf_sdkconfig_option(
 | 
			
		||||
                "CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY",
 | 
			
		||||
                not config.get(CONF_VERIFY_SSL),
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            cg.add_library("WiFiClientSecure", None)
 | 
			
		||||
            cg.add_library("HTTPClient", None)
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        cg.add_library("ESP8266HTTPClient", None)
 | 
			
		||||
    if CORE.is_rp2040 and CORE.using_arduino:
 | 
			
		||||
        cg.add_library("HTTPClient", None)
 | 
			
		||||
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await cg.register_parented(var, config[CONF_HTTP_REQUEST_ID])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA = cv.All(
 | 
			
		||||
@@ -148,7 +57,9 @@ OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA = cv.All(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.use_id(OtaHttpRequestComponent),
 | 
			
		||||
            cv.Optional(CONF_MD5_URL): cv.templatable(cv.url),
 | 
			
		||||
            cv.Optional(CONF_MD5): cv.templatable(cv.string),
 | 
			
		||||
            cv.Optional(CONF_MD5): cv.templatable(
 | 
			
		||||
                cv.All(cv.string, cv.Length(min=32, max=32))
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PASSWORD): cv.templatable(cv.string),
 | 
			
		||||
            cv.Optional(CONF_USERNAME): cv.templatable(cv.string),
 | 
			
		||||
            cv.Required(CONF_URL): cv.templatable(cv.url),
 | 
			
		||||
@@ -159,7 +70,7 @@ OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ota_http_request.flash",
 | 
			
		||||
    "ota.http_request.flash",
 | 
			
		||||
    OtaHttpRequestComponentFlashAction,
 | 
			
		||||
    OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,45 +1,29 @@
 | 
			
		||||
#include "ota_http_request.h"
 | 
			
		||||
#include "watchdog.h"
 | 
			
		||||
#include "../watchdog.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/md5/md5.h"
 | 
			
		||||
#include "esphome/components/ota/ota_backend.h"
 | 
			
		||||
#include "esphome/components/ota/ota_backend_arduino_esp32.h"
 | 
			
		||||
#include "esphome/components/ota/ota_backend_arduino_esp8266.h"
 | 
			
		||||
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
 | 
			
		||||
#include "esphome/components/ota/ota_backend_esp_idf.h"
 | 
			
		||||
#include "esphome/components/ota/ota_backend.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "http_request.ota";
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponent::setup() {
 | 
			
		||||
#ifdef USE_OTA_STATE_CALLBACK
 | 
			
		||||
  ota::register_ota_platform(this);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Over-The-Air updates via HTTP request:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Timeout: %llus", this->timeout_ / 1000);
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  ESP8266 SSL support: No");
 | 
			
		||||
#else
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  ESP8266 SSL support: Yes");
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  TLS server verification: Yes");
 | 
			
		||||
#else
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  TLS server verification: No");
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Watchdog timeout: %ds", USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT / 1000);
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
void OtaHttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air updates via HTTP request"); };
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponent::set_md5_url(const std::string &url) {
 | 
			
		||||
  if (!this->validate_url_(url)) {
 | 
			
		||||
@@ -58,20 +42,6 @@ void OtaHttpRequestComponent::set_url(const std::string &url) {
 | 
			
		||||
  this->url_ = url;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool OtaHttpRequestComponent::check_status() {
 | 
			
		||||
  // status can be -1, or HTTP status code
 | 
			
		||||
  if (this->status_ < 100) {
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP server did not respond (error %d)", this->status_);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->status_ >= 310) {
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP error %d", this->status_);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "HTTP status %d", this->status_);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponent::flash() {
 | 
			
		||||
  if (this->url_.empty()) {
 | 
			
		||||
    ESP_LOGE(TAG, "URL not set; cannot start update");
 | 
			
		||||
@@ -104,17 +74,18 @@ void OtaHttpRequestComponent::flash() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponent::cleanup_(std::unique_ptr<ota::OTABackend> backend) {
 | 
			
		||||
void OtaHttpRequestComponent::cleanup_(std::unique_ptr<ota::OTABackend> backend,
 | 
			
		||||
                                       const std::shared_ptr<HttpContainer> &container) {
 | 
			
		||||
  if (this->update_started_) {
 | 
			
		||||
    ESP_LOGV(TAG, "Aborting OTA backend");
 | 
			
		||||
    backend->abort();
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "Aborting HTTP connection");
 | 
			
		||||
  this->http_end();
 | 
			
		||||
  container->end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
uint8_t OtaHttpRequestComponent::do_ota_() {
 | 
			
		||||
  uint8_t buf[this->http_recv_buffer_ + 1];
 | 
			
		||||
  uint8_t buf[OtaHttpRequestComponent::HTTP_RECV_BUFFER + 1];
 | 
			
		||||
  uint32_t last_progress = 0;
 | 
			
		||||
  uint32_t update_start_time = millis();
 | 
			
		||||
  md5::MD5Digest md5_receive;
 | 
			
		||||
@@ -132,9 +103,10 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
 | 
			
		||||
  ESP_LOGI(TAG, "Connecting to: %s", this->url_.c_str());
 | 
			
		||||
  this->http_init(url_with_auth);
 | 
			
		||||
  if (!this->check_status()) {
 | 
			
		||||
    this->http_end();
 | 
			
		||||
 | 
			
		||||
  auto container = this->parent_->get(url_with_auth);
 | 
			
		||||
 | 
			
		||||
  if (container == nullptr) {
 | 
			
		||||
    return OTA_CONNECTION_ERROR;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -144,18 +116,18 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "OTA backend begin");
 | 
			
		||||
  auto backend = ota::make_ota_backend();
 | 
			
		||||
  auto error_code = backend->begin(this->body_length_);
 | 
			
		||||
  auto error_code = backend->begin(container->content_length);
 | 
			
		||||
  if (error_code != ota::OTA_RESPONSE_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "backend->begin error: %d", error_code);
 | 
			
		||||
    this->cleanup_(std::move(backend));
 | 
			
		||||
    this->cleanup_(std::move(backend), container);
 | 
			
		||||
    return error_code;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->bytes_read_ = 0;
 | 
			
		||||
  while (this->bytes_read_ < this->body_length_) {
 | 
			
		||||
  while (container->get_bytes_read() < container->content_length) {
 | 
			
		||||
    // read a maximum of chunk_size bytes into buf. (real read size returned)
 | 
			
		||||
    int bufsize = this->http_read(buf, this->http_recv_buffer_);
 | 
			
		||||
    ESP_LOGVV(TAG, "bytes_read_ = %u, body_length_ = %u, bufsize = %i", this->bytes_read_, this->body_length_, bufsize);
 | 
			
		||||
    int bufsize = container->read(buf, OtaHttpRequestComponent::HTTP_RECV_BUFFER);
 | 
			
		||||
    ESP_LOGVV(TAG, "bytes_read_ = %u, body_length_ = %u, bufsize = %i", container->get_bytes_read(),
 | 
			
		||||
              container->content_length, bufsize);
 | 
			
		||||
 | 
			
		||||
    // feed watchdog and give other tasks a chance to run
 | 
			
		||||
    App.feed_wdt();
 | 
			
		||||
@@ -163,9 +135,9 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
 | 
			
		||||
 | 
			
		||||
    if (bufsize < 0) {
 | 
			
		||||
      ESP_LOGE(TAG, "Stream closed");
 | 
			
		||||
      this->cleanup_(std::move(backend));
 | 
			
		||||
      this->cleanup_(std::move(backend), container);
 | 
			
		||||
      return OTA_CONNECTION_ERROR;
 | 
			
		||||
    } else if (bufsize > 0 && bufsize <= this->http_recv_buffer_) {
 | 
			
		||||
    } else if (bufsize > 0 && bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) {
 | 
			
		||||
      // add read bytes to MD5
 | 
			
		||||
      md5_receive.add(buf, bufsize);
 | 
			
		||||
 | 
			
		||||
@@ -176,16 +148,16 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
 | 
			
		||||
        // error code explanation available at
 | 
			
		||||
        // https://github.com/esphome/esphome/blob/dev/esphome/components/ota/ota_backend.h
 | 
			
		||||
        ESP_LOGE(TAG, "Error code (%02X) writing binary data to flash at offset %d and size %d", error_code,
 | 
			
		||||
                 this->bytes_read_ - bufsize, this->body_length_);
 | 
			
		||||
        this->cleanup_(std::move(backend));
 | 
			
		||||
                 container->get_bytes_read() - bufsize, container->content_length);
 | 
			
		||||
        this->cleanup_(std::move(backend), container);
 | 
			
		||||
        return error_code;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    uint32_t now = millis();
 | 
			
		||||
    if ((now - last_progress > 1000) or (this->bytes_read_ == this->body_length_)) {
 | 
			
		||||
    if ((now - last_progress > 1000) or (container->get_bytes_read() == container->content_length)) {
 | 
			
		||||
      last_progress = now;
 | 
			
		||||
      float percentage = this->bytes_read_ * 100.0f / this->body_length_;
 | 
			
		||||
      float percentage = container->get_bytes_read() * 100.0f / container->content_length;
 | 
			
		||||
      ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
 | 
			
		||||
#ifdef USE_OTA_STATE_CALLBACK
 | 
			
		||||
      this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
 | 
			
		||||
@@ -201,13 +173,13 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
 | 
			
		||||
  this->md5_computed_ = md5_receive_str.get();
 | 
			
		||||
  if (strncmp(this->md5_computed_.c_str(), this->md5_expected_.c_str(), MD5_SIZE) != 0) {
 | 
			
		||||
    ESP_LOGE(TAG, "MD5 computed: %s - Aborting due to MD5 mismatch", this->md5_computed_.c_str());
 | 
			
		||||
    this->cleanup_(std::move(backend));
 | 
			
		||||
    this->cleanup_(std::move(backend), container);
 | 
			
		||||
    return ota::OTA_RESPONSE_ERROR_MD5_MISMATCH;
 | 
			
		||||
  } else {
 | 
			
		||||
    backend->set_update_md5(md5_receive_str.get());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->http_end();
 | 
			
		||||
  container->end();
 | 
			
		||||
 | 
			
		||||
  // feed watchdog and give other tasks a chance to run
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
@@ -217,7 +189,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
 | 
			
		||||
  error_code = backend->end();
 | 
			
		||||
  if (error_code != ota::OTA_RESPONSE_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error ending update! error_code: %d", error_code);
 | 
			
		||||
    this->cleanup_(std::move(backend));
 | 
			
		||||
    this->cleanup_(std::move(backend), container);
 | 
			
		||||
    return error_code;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -256,28 +228,32 @@ bool OtaHttpRequestComponent::http_get_md5_() {
 | 
			
		||||
 | 
			
		||||
  ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
 | 
			
		||||
  ESP_LOGI(TAG, "Connecting to: %s", this->md5_url_.c_str());
 | 
			
		||||
  this->http_init(url_with_auth);
 | 
			
		||||
  if (!this->check_status()) {
 | 
			
		||||
    this->http_end();
 | 
			
		||||
  auto container = this->parent_->get(url_with_auth);
 | 
			
		||||
  if (container == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to connect to MD5 URL");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  int length = this->body_length_;
 | 
			
		||||
  if (length < 0) {
 | 
			
		||||
    this->http_end();
 | 
			
		||||
  size_t length = container->content_length;
 | 
			
		||||
  if (length == 0) {
 | 
			
		||||
    container->end();
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (length < MD5_SIZE) {
 | 
			
		||||
    ESP_LOGE(TAG, "MD5 file must be %u bytes; %u bytes reported by HTTP server. Aborting", MD5_SIZE,
 | 
			
		||||
             this->body_length_);
 | 
			
		||||
    this->http_end();
 | 
			
		||||
    ESP_LOGE(TAG, "MD5 file must be %u bytes; %u bytes reported by HTTP server. Aborting", MD5_SIZE, length);
 | 
			
		||||
    container->end();
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->bytes_read_ = 0;
 | 
			
		||||
  this->md5_expected_.resize(MD5_SIZE);
 | 
			
		||||
  auto read_len = this->http_read((uint8_t *) this->md5_expected_.data(), MD5_SIZE);
 | 
			
		||||
  this->http_end();
 | 
			
		||||
  int read_len = 0;
 | 
			
		||||
  while (container->get_bytes_read() < MD5_SIZE) {
 | 
			
		||||
    read_len = container->read((uint8_t *) this->md5_expected_.data(), MD5_SIZE);
 | 
			
		||||
    App.feed_wdt();
 | 
			
		||||
    yield();
 | 
			
		||||
  }
 | 
			
		||||
  container->end();
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Read len: %u, MD5 expected: %u", read_len, MD5_SIZE);
 | 
			
		||||
  return read_len == MD5_SIZE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,19 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/ota/ota_backend.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/components/ota/ota_backend.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include "../http_request.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "http_request.ota";
 | 
			
		||||
static const uint8_t MD5_SIZE = 32;
 | 
			
		||||
 | 
			
		||||
enum OtaHttpRequestError : uint8_t {
 | 
			
		||||
@@ -20,7 +22,7 @@ enum OtaHttpRequestError : uint8_t {
 | 
			
		||||
  OTA_CONNECTION_ERROR = 0x12,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class OtaHttpRequestComponent : public ota::OTAComponent {
 | 
			
		||||
class OtaHttpRequestComponent : public ota::OTAComponent, public Parented<HttpRequestComponent> {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
@@ -29,27 +31,19 @@ class OtaHttpRequestComponent : public ota::OTAComponent {
 | 
			
		||||
  void set_md5_url(const std::string &md5_url);
 | 
			
		||||
  void set_md5(const std::string &md5) { this->md5_expected_ = md5; }
 | 
			
		||||
  void set_password(const std::string &password) { this->password_ = password; }
 | 
			
		||||
  void set_timeout(const uint64_t timeout) { this->timeout_ = timeout; }
 | 
			
		||||
  void set_url(const std::string &url);
 | 
			
		||||
  void set_username(const std::string &username) { this->username_ = username; }
 | 
			
		||||
 | 
			
		||||
  std::string md5_computed() { return this->md5_computed_; }
 | 
			
		||||
  std::string md5_expected() { return this->md5_expected_; }
 | 
			
		||||
 | 
			
		||||
  bool check_status();
 | 
			
		||||
 | 
			
		||||
  void flash();
 | 
			
		||||
 | 
			
		||||
  virtual void http_init(const std::string &url){};
 | 
			
		||||
  virtual int http_read(uint8_t *buf, size_t len) { return 0; };
 | 
			
		||||
  virtual void http_end(){};
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void cleanup_(std::unique_ptr<ota::OTABackend> backend);
 | 
			
		||||
  void cleanup_(std::unique_ptr<ota::OTABackend> backend, const std::shared_ptr<HttpContainer> &container);
 | 
			
		||||
  uint8_t do_ota_();
 | 
			
		||||
  std::string get_url_with_auth_(const std::string &url);
 | 
			
		||||
  bool http_get_md5_();
 | 
			
		||||
  bool secure_() { return this->url_.find("https:") != std::string::npos; };
 | 
			
		||||
  bool validate_url_(const std::string &url);
 | 
			
		||||
 | 
			
		||||
  std::string md5_computed_{};
 | 
			
		||||
@@ -58,14 +52,9 @@ class OtaHttpRequestComponent : public ota::OTAComponent {
 | 
			
		||||
  std::string password_{};
 | 
			
		||||
  std::string username_{};
 | 
			
		||||
  std::string url_{};
 | 
			
		||||
  size_t body_length_ = 0;
 | 
			
		||||
  size_t bytes_read_ = 0;
 | 
			
		||||
  int status_ = -1;
 | 
			
		||||
  uint64_t timeout_ = 0;
 | 
			
		||||
  bool update_started_ = false;
 | 
			
		||||
  const uint16_t http_recv_buffer_ = 256;      // the firmware GET chunk size
 | 
			
		||||
  const uint16_t max_http_recv_buffer_ = 512;  // internal max http buffer size must be > HTTP_RECV_BUFFER_ (TLS
 | 
			
		||||
                                               // overhead) and must be a power of two from 512 to 4096
 | 
			
		||||
  static const uint16_t HTTP_RECV_BUFFER = 256;  // the firmware GET chunk size
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
 
 | 
			
		||||
@@ -1,134 +0,0 @@
 | 
			
		||||
#include "ota_http_request.h"
 | 
			
		||||
#include "watchdog.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#include "ota_http_request_arduino.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
#include "esphome/components/md5/md5.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
struct Header {
 | 
			
		||||
  const char *name;
 | 
			
		||||
  const char *value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponentArduino::http_init(const std::string &url) {
 | 
			
		||||
  const char *header_keys[] = {"Content-Length", "Content-Type"};
 | 
			
		||||
  const size_t header_count = sizeof(header_keys) / sizeof(header_keys[0]);
 | 
			
		||||
  watchdog::WatchdogManager wdts;
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
  if (this->stream_ptr_ == nullptr && this->set_stream_ptr_()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Unable to set client");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_ESP8266
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  this->client_.setInsecure();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_RP2040)
 | 
			
		||||
  this->status_ = this->client_.begin(url.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
  this->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
 | 
			
		||||
  this->status_ = this->client_.begin(*this->stream_ptr_, url.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (!this->status_) {
 | 
			
		||||
    this->client_.end();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->client_.setReuse(true);
 | 
			
		||||
 | 
			
		||||
  // returned needed headers must be collected before the requests
 | 
			
		||||
  this->client_.collectHeaders(header_keys, header_count);
 | 
			
		||||
 | 
			
		||||
  // HTTP GET
 | 
			
		||||
  this->status_ = this->client_.GET();
 | 
			
		||||
 | 
			
		||||
  this->body_length_ = (size_t) this->client_.getSize();
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_RP2040)
 | 
			
		||||
  if (this->stream_ptr_ == nullptr) {
 | 
			
		||||
    this->set_stream_ptr_();
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int OtaHttpRequestComponentArduino::http_read(uint8_t *buf, const size_t max_len) {
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0)  // && USE_ARDUINO_VERSION_CODE < VERSION_CODE(?, ?, ?)
 | 
			
		||||
  if (!this->secure_()) {
 | 
			
		||||
    ESP_LOGW(TAG, "Using HTTP on Arduino version >= 3.1 is **very** slow. Consider setting framework version to 3.0.2 "
 | 
			
		||||
                  "in your YAML, or use HTTPS");
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_ARDUINO_VERSION_CODE
 | 
			
		||||
#endif  // USE_ESP8266
 | 
			
		||||
 | 
			
		||||
  watchdog::WatchdogManager wdts;
 | 
			
		||||
 | 
			
		||||
  // Since arduino8266 >= 3.1 using this->stream_ptr_ is broken (https://github.com/esp8266/Arduino/issues/9035)
 | 
			
		||||
  WiFiClient *stream_ptr = this->client_.getStreamPtr();
 | 
			
		||||
  if (stream_ptr == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Stream pointer vanished!");
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int available_data = stream_ptr->available();
 | 
			
		||||
  int bufsize = std::min((int) max_len, available_data);
 | 
			
		||||
  if (bufsize > 0) {
 | 
			
		||||
    stream_ptr->readBytes(buf, bufsize);
 | 
			
		||||
    this->bytes_read_ += bufsize;
 | 
			
		||||
    buf[bufsize] = '\0';  // not fed to ota
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return bufsize;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponentArduino::http_end() {
 | 
			
		||||
  watchdog::WatchdogManager wdts;
 | 
			
		||||
  this->client_.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int OtaHttpRequestComponentArduino::set_stream_ptr_() {
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
  if (this->secure_()) {
 | 
			
		||||
    ESP_LOGV(TAG, "ESP8266 HTTPS connection with WiFiClientSecure");
 | 
			
		||||
    this->stream_ptr_ = std::make_unique<WiFiClientSecure>();
 | 
			
		||||
    WiFiClientSecure *secure_client = static_cast<WiFiClientSecure *>(this->stream_ptr_.get());
 | 
			
		||||
    secure_client->setBufferSizes(this->max_http_recv_buffer_, 512);
 | 
			
		||||
    secure_client->setInsecure();
 | 
			
		||||
  } else {
 | 
			
		||||
    this->stream_ptr_ = std::make_unique<WiFiClient>();
 | 
			
		||||
  }
 | 
			
		||||
#else
 | 
			
		||||
  ESP_LOGV(TAG, "ESP8266 HTTP connection with WiFiClient");
 | 
			
		||||
  if (this->secure_()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Can't use HTTPS connection with esp8266_disable_ssl_support");
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
  this->stream_ptr_ = std::make_unique<WiFiClient>();
 | 
			
		||||
#endif  // USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
#endif  // USE_ESP8266
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_RP2040)
 | 
			
		||||
  this->stream_ptr_ = std::unique_ptr<WiFiClient>(this->client_.getStreamPtr());
 | 
			
		||||
#endif
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
@@ -1,42 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "ota_http_request.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_RP2040)
 | 
			
		||||
#include <HTTPClient.h>
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
#include <ESP8266HTTPClient.h>
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
#include <WiFiClientSecure.h>
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
class OtaHttpRequestComponentArduino : public OtaHttpRequestComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  void http_init(const std::string &url) override;
 | 
			
		||||
  int http_read(uint8_t *buf, size_t len) override;
 | 
			
		||||
  void http_end() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  int set_stream_ptr_();
 | 
			
		||||
  HTTPClient client_{};
 | 
			
		||||
  std::unique_ptr<WiFiClient> stream_ptr_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
@@ -1,86 +0,0 @@
 | 
			
		||||
#include "ota_http_request_idf.h"
 | 
			
		||||
#include "watchdog.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/components/md5/md5.h"
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
 | 
			
		||||
#include "esp_event.h"
 | 
			
		||||
#include "esp_http_client.h"
 | 
			
		||||
#include "esp_idf_version.h"
 | 
			
		||||
#include "esp_log.h"
 | 
			
		||||
#include "esp_netif.h"
 | 
			
		||||
#include "esp_system.h"
 | 
			
		||||
#include "esp_task_wdt.h"
 | 
			
		||||
#include "esp_tls.h"
 | 
			
		||||
 | 
			
		||||
#include "freertos/FreeRTOS.h"
 | 
			
		||||
#include "freertos/task.h"
 | 
			
		||||
#include "nvs_flash.h"
 | 
			
		||||
 | 
			
		||||
#include <cctype>
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <sys/param.h>
 | 
			
		||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
 | 
			
		||||
#include "esp_crt_bundle.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponentIDF::http_init(const std::string &url) {
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
#pragma GCC diagnostic push
 | 
			
		||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
 | 
			
		||||
  esp_http_client_config_t config = {nullptr};
 | 
			
		||||
  config.url = url.c_str();
 | 
			
		||||
  config.method = HTTP_METHOD_GET;
 | 
			
		||||
  config.timeout_ms = (int) this->timeout_;
 | 
			
		||||
  config.buffer_size = this->max_http_recv_buffer_;
 | 
			
		||||
  config.auth_type = HTTP_AUTH_TYPE_BASIC;
 | 
			
		||||
  config.max_authorization_retries = -1;
 | 
			
		||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
 | 
			
		||||
  if (this->secure_()) {
 | 
			
		||||
    config.crt_bundle_attach = esp_crt_bundle_attach;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
#pragma GCC diagnostic pop
 | 
			
		||||
 | 
			
		||||
  watchdog::WatchdogManager wdts;
 | 
			
		||||
  this->client_ = esp_http_client_init(&config);
 | 
			
		||||
  if ((this->status_ = esp_http_client_open(this->client_, 0)) == ESP_OK) {
 | 
			
		||||
    this->body_length_ = esp_http_client_fetch_headers(this->client_);
 | 
			
		||||
    this->status_ = esp_http_client_get_status_code(this->client_);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int OtaHttpRequestComponentIDF::http_read(uint8_t *buf, const size_t max_len) {
 | 
			
		||||
  watchdog::WatchdogManager wdts;
 | 
			
		||||
  int bufsize = std::min(max_len, this->body_length_ - this->bytes_read_);
 | 
			
		||||
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
  int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize);
 | 
			
		||||
  if (read_len > 0) {
 | 
			
		||||
    this->bytes_read_ += bufsize;
 | 
			
		||||
    buf[bufsize] = '\0';  // not fed to ota
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return read_len;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponentIDF::http_end() {
 | 
			
		||||
  watchdog::WatchdogManager wdts;
 | 
			
		||||
 | 
			
		||||
  esp_http_client_close(this->client_);
 | 
			
		||||
  esp_http_client_cleanup(this->client_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "ota_http_request.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
#include "esp_http_client.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
class OtaHttpRequestComponentIDF : public OtaHttpRequestComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  void http_init(const std::string &url) override;
 | 
			
		||||
  int http_read(uint8_t *buf, size_t len) override;
 | 
			
		||||
  void http_end() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  esp_http_client_handle_t client_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
							
								
								
									
										44
									
								
								esphome/components/http_request/update/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								esphome/components/http_request/update/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
from esphome.components import update
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_SOURCE,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from .. import http_request_ns, CONF_HTTP_REQUEST_ID, HttpRequestComponent
 | 
			
		||||
from ..ota import OtaHttpRequestComponent
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["json"]
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
DEPENDENCIES = ["ota.http_request"]
 | 
			
		||||
 | 
			
		||||
HttpRequestUpdate = http_request_ns.class_(
 | 
			
		||||
    "HttpRequestUpdate", update.UpdateEntity, cg.PollingComponent
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_OTA_ID = "ota_id"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = update.UPDATE_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(HttpRequestUpdate),
 | 
			
		||||
        cv.GenerateID(CONF_OTA_ID): cv.use_id(OtaHttpRequestComponent),
 | 
			
		||||
        cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
 | 
			
		||||
        cv.Required(CONF_SOURCE): cv.url,
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.polling_component_schema("6h"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await update.new_update(config)
 | 
			
		||||
    ota_parent = await cg.get_variable(config[CONF_OTA_ID])
 | 
			
		||||
    cg.add(var.set_ota_parent(ota_parent))
 | 
			
		||||
    request_parent = await cg.get_variable(config[CONF_HTTP_REQUEST_ID])
 | 
			
		||||
    cg.add(var.set_request_parent(request_parent))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_source_url(config[CONF_SOURCE]))
 | 
			
		||||
 | 
			
		||||
    cg.add_define("USE_OTA_STATE_CALLBACK")
 | 
			
		||||
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
							
								
								
									
										157
									
								
								esphome/components/http_request/update/http_request_update.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								esphome/components/http_request/update/http_request_update.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
#include "http_request_update.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/version.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/json/json_util.h"
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "http_request.update";
 | 
			
		||||
 | 
			
		||||
static const size_t MAX_READ_SIZE = 256;
 | 
			
		||||
 | 
			
		||||
void HttpRequestUpdate::setup() {
 | 
			
		||||
  this->ota_parent_->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t err) {
 | 
			
		||||
    if (state == ota::OTAState::OTA_IN_PROGRESS) {
 | 
			
		||||
      this->state_ = update::UPDATE_STATE_INSTALLING;
 | 
			
		||||
      this->update_info_.has_progress = true;
 | 
			
		||||
      this->update_info_.progress = progress;
 | 
			
		||||
      this->publish_state();
 | 
			
		||||
    } else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
 | 
			
		||||
      this->state_ = update::UPDATE_STATE_AVAILABLE;
 | 
			
		||||
      this->status_set_error("Failed to install firmware");
 | 
			
		||||
      this->publish_state();
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HttpRequestUpdate::update() {
 | 
			
		||||
  auto container = this->request_parent_->get(this->source_url_);
 | 
			
		||||
 | 
			
		||||
  if (container == nullptr) {
 | 
			
		||||
    std::string msg = str_sprintf("Failed to fetch manifest from %s", this->source_url_.c_str());
 | 
			
		||||
    this->status_set_error(msg.c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
 | 
			
		||||
  uint8_t *data = allocator.allocate(container->content_length);
 | 
			
		||||
  if (data == nullptr) {
 | 
			
		||||
    std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length);
 | 
			
		||||
    this->status_set_error(msg.c_str());
 | 
			
		||||
    container->end();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  size_t read_index = 0;
 | 
			
		||||
  while (container->get_bytes_read() < container->content_length) {
 | 
			
		||||
    int read_bytes = container->read(data + read_index, MAX_READ_SIZE);
 | 
			
		||||
 | 
			
		||||
    App.feed_wdt();
 | 
			
		||||
    yield();
 | 
			
		||||
 | 
			
		||||
    read_index += read_bytes;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::string response((char *) data, read_index);
 | 
			
		||||
  allocator.deallocate(data, container->content_length);
 | 
			
		||||
 | 
			
		||||
  container->end();
 | 
			
		||||
 | 
			
		||||
  bool valid = json::parse_json(response, [this](JsonObject root) -> bool {
 | 
			
		||||
    if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) {
 | 
			
		||||
      ESP_LOGE(TAG, "Manifest does not contain required fields");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    this->update_info_.title = root["name"].as<std::string>();
 | 
			
		||||
    this->update_info_.latest_version = root["version"].as<std::string>();
 | 
			
		||||
 | 
			
		||||
    for (auto build : root["builds"].as<JsonArray>()) {
 | 
			
		||||
      if (!build.containsKey("chipFamily")) {
 | 
			
		||||
        ESP_LOGE(TAG, "Manifest does not contain required fields");
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      if (build["chipFamily"] == ESPHOME_VARIANT) {
 | 
			
		||||
        if (!build.containsKey("ota")) {
 | 
			
		||||
          ESP_LOGE(TAG, "Manifest does not contain required fields");
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
        auto ota = build["ota"];
 | 
			
		||||
        if (!ota.containsKey("path") || !ota.containsKey("md5")) {
 | 
			
		||||
          ESP_LOGE(TAG, "Manifest does not contain required fields");
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
        this->update_info_.firmware_url = ota["path"].as<std::string>();
 | 
			
		||||
        this->update_info_.md5 = ota["md5"].as<std::string>();
 | 
			
		||||
 | 
			
		||||
        if (ota.containsKey("summary"))
 | 
			
		||||
          this->update_info_.summary = ota["summary"].as<std::string>();
 | 
			
		||||
        if (ota.containsKey("release_url"))
 | 
			
		||||
          this->update_info_.release_url = ota["release_url"].as<std::string>();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  if (!valid) {
 | 
			
		||||
    std::string msg = str_sprintf("Failed to parse JSON from %s", this->source_url_.c_str());
 | 
			
		||||
    this->status_set_error(msg.c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Merge source_url_ and this->update_info_.firmware_url
 | 
			
		||||
  if (this->update_info_.firmware_url.find("http") == std::string::npos) {
 | 
			
		||||
    std::string path = this->update_info_.firmware_url;
 | 
			
		||||
    if (path[0] == '/') {
 | 
			
		||||
      std::string domain = this->source_url_.substr(0, this->source_url_.find('/', 8));
 | 
			
		||||
      this->update_info_.firmware_url = domain + path;
 | 
			
		||||
    } else {
 | 
			
		||||
      std::string domain = this->source_url_.substr(0, this->source_url_.rfind('/') + 1);
 | 
			
		||||
      this->update_info_.firmware_url = domain + path;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::string current_version = this->current_version_;
 | 
			
		||||
  if (current_version.empty()) {
 | 
			
		||||
#ifdef ESPHOME_PROJECT_VERSION
 | 
			
		||||
    current_version = ESPHOME_PROJECT_VERSION;
 | 
			
		||||
#else
 | 
			
		||||
    current_version = ESPHOME_VERSION;
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
  this->update_info_.current_version = current_version;
 | 
			
		||||
 | 
			
		||||
  if (this->update_info_.latest_version.empty()) {
 | 
			
		||||
    this->state_ = update::UPDATE_STATE_NO_UPDATE;
 | 
			
		||||
  } else if (this->update_info_.latest_version != this->current_version_) {
 | 
			
		||||
    this->state_ = update::UPDATE_STATE_AVAILABLE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->update_info_.has_progress = false;
 | 
			
		||||
  this->update_info_.progress = 0.0f;
 | 
			
		||||
 | 
			
		||||
  this->status_clear_error();
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HttpRequestUpdate::perform() {
 | 
			
		||||
  if (this->state_ != update::UPDATE_STATE_AVAILABLE) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->state_ = update::UPDATE_STATE_INSTALLING;
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
 | 
			
		||||
  this->ota_parent_->set_md5(this->update_info.md5);
 | 
			
		||||
  this->ota_parent_->set_url(this->update_info.firmware_url);
 | 
			
		||||
  // Flash in the next loop
 | 
			
		||||
  this->defer([this]() { this->ota_parent_->flash(); });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										37
									
								
								esphome/components/http_request/update/http_request_update.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								esphome/components/http_request/update/http_request_update.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/http_request/http_request.h"
 | 
			
		||||
#include "esphome/components/http_request/ota/ota_http_request.h"
 | 
			
		||||
#include "esphome/components/update/update_entity.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
  void perform() override;
 | 
			
		||||
 | 
			
		||||
  void set_source_url(const std::string &source_url) { this->source_url_ = source_url; }
 | 
			
		||||
 | 
			
		||||
  void set_request_parent(HttpRequestComponent *request_parent) { this->request_parent_ = request_parent; }
 | 
			
		||||
  void set_ota_parent(OtaHttpRequestComponent *ota_parent) { this->ota_parent_ = ota_parent; }
 | 
			
		||||
 | 
			
		||||
  void set_current_version(const std::string ¤t_version) { this->current_version_ = current_version; }
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  HttpRequestComponent *request_parent_;
 | 
			
		||||
  OtaHttpRequestComponent *ota_parent_;
 | 
			
		||||
  std::string source_url_;
 | 
			
		||||
  std::string current_version_{""};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
#include "watchdog.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
@@ -20,14 +18,22 @@ namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
namespace watchdog {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "watchdog.http_request.ota";
 | 
			
		||||
static const char *const TAG = "http_request.watchdog";
 | 
			
		||||
 | 
			
		||||
WatchdogManager::WatchdogManager() {
 | 
			
		||||
WatchdogManager::WatchdogManager(uint32_t timeout_ms) : timeout_ms_(timeout_ms) {
 | 
			
		||||
  if (timeout_ms == 0) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->saved_timeout_ms_ = this->get_timeout_();
 | 
			
		||||
  this->set_timeout_(USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT);
 | 
			
		||||
  this->set_timeout_(timeout_ms);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WatchdogManager::~WatchdogManager() { this->set_timeout_(this->saved_timeout_ms_); }
 | 
			
		||||
WatchdogManager::~WatchdogManager() {
 | 
			
		||||
  if (this->timeout_ms_ == 0) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->set_timeout_(this->saved_timeout_ms_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchdogManager::set_timeout_(uint32_t timeout_ms) {
 | 
			
		||||
  ESP_LOGV(TAG, "Adjusting WDT to %" PRIu32 "ms", timeout_ms);
 | 
			
		||||
@@ -40,7 +46,7 @@ void WatchdogManager::set_timeout_(uint32_t timeout_ms) {
 | 
			
		||||
  };
 | 
			
		||||
  esp_task_wdt_reconfigure(&wdt_config);
 | 
			
		||||
#else
 | 
			
		||||
  esp_task_wdt_init(timeout_ms, true);
 | 
			
		||||
  esp_task_wdt_init(timeout_ms / 1000, true);
 | 
			
		||||
#endif  // ESP_IDF_VERSION_MAJOR
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
@@ -68,4 +74,3 @@ uint32_t WatchdogManager::get_timeout_() {
 | 
			
		||||
}  // namespace watchdog
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
#endif
 | 
			
		||||
@@ -9,9 +9,8 @@ namespace http_request {
 | 
			
		||||
namespace watchdog {
 | 
			
		||||
 | 
			
		||||
class WatchdogManager {
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT
 | 
			
		||||
 public:
 | 
			
		||||
  WatchdogManager();
 | 
			
		||||
  WatchdogManager(uint32_t timeout_ms);
 | 
			
		||||
  ~WatchdogManager();
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
@@ -19,7 +18,7 @@ class WatchdogManager {
 | 
			
		||||
  void set_timeout_(uint32_t timeout_ms);
 | 
			
		||||
 | 
			
		||||
  uint32_t saved_timeout_ms_{0};
 | 
			
		||||
#endif
 | 
			
		||||
  uint32_t timeout_ms_{0};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace watchdog
 | 
			
		||||
@@ -27,43 +27,24 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
 | 
			
		||||
      this->start();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (play_state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING) {
 | 
			
		||||
    this->is_announcement_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (call.get_volume().has_value()) {
 | 
			
		||||
    this->volume = call.get_volume().value();
 | 
			
		||||
    this->set_volume_(volume);
 | 
			
		||||
    this->unmute_();
 | 
			
		||||
  }
 | 
			
		||||
  if (this->i2s_state_ != I2S_STATE_RUNNING) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (call.get_command().has_value()) {
 | 
			
		||||
    switch (call.get_command().value()) {
 | 
			
		||||
      case media_player::MEDIA_PLAYER_COMMAND_PLAY:
 | 
			
		||||
        if (!this->audio_->isRunning())
 | 
			
		||||
          this->audio_->pauseResume();
 | 
			
		||||
        this->state = play_state;
 | 
			
		||||
        break;
 | 
			
		||||
      case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
 | 
			
		||||
        if (this->audio_->isRunning())
 | 
			
		||||
          this->audio_->pauseResume();
 | 
			
		||||
        this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
 | 
			
		||||
        break;
 | 
			
		||||
      case media_player::MEDIA_PLAYER_COMMAND_STOP:
 | 
			
		||||
        this->stop();
 | 
			
		||||
        break;
 | 
			
		||||
      case media_player::MEDIA_PLAYER_COMMAND_MUTE:
 | 
			
		||||
        this->mute_();
 | 
			
		||||
        break;
 | 
			
		||||
      case media_player::MEDIA_PLAYER_COMMAND_UNMUTE:
 | 
			
		||||
        this->unmute_();
 | 
			
		||||
        break;
 | 
			
		||||
      case media_player::MEDIA_PLAYER_COMMAND_TOGGLE:
 | 
			
		||||
        this->audio_->pauseResume();
 | 
			
		||||
        if (this->audio_->isRunning()) {
 | 
			
		||||
          this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
 | 
			
		||||
        } else {
 | 
			
		||||
          this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      case media_player::MEDIA_PLAYER_COMMAND_VOLUME_UP: {
 | 
			
		||||
        float new_volume = this->volume + 0.1f;
 | 
			
		||||
        if (new_volume > 1.0f)
 | 
			
		||||
@@ -80,6 +61,36 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
 | 
			
		||||
        this->unmute_();
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->i2s_state_ != I2S_STATE_RUNNING) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    switch (call.get_command().value()) {
 | 
			
		||||
      case media_player::MEDIA_PLAYER_COMMAND_PLAY:
 | 
			
		||||
        if (!this->audio_->isRunning())
 | 
			
		||||
          this->audio_->pauseResume();
 | 
			
		||||
        this->state = play_state;
 | 
			
		||||
        break;
 | 
			
		||||
      case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
 | 
			
		||||
        if (this->audio_->isRunning())
 | 
			
		||||
          this->audio_->pauseResume();
 | 
			
		||||
        this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
 | 
			
		||||
        break;
 | 
			
		||||
      case media_player::MEDIA_PLAYER_COMMAND_STOP:
 | 
			
		||||
        this->stop();
 | 
			
		||||
        break;
 | 
			
		||||
      case media_player::MEDIA_PLAYER_COMMAND_TOGGLE:
 | 
			
		||||
        this->audio_->pauseResume();
 | 
			
		||||
        if (this->audio_->isRunning()) {
 | 
			
		||||
          this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
 | 
			
		||||
        } else {
 | 
			
		||||
          this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
@@ -171,9 +182,8 @@ void I2SAudioMediaPlayer::start_() {
 | 
			
		||||
  if (this->current_url_.has_value()) {
 | 
			
		||||
    this->audio_->connecttohost(this->current_url_.value().c_str());
 | 
			
		||||
    this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
 | 
			
		||||
    if (this->is_announcement_.has_value()) {
 | 
			
		||||
      this->state = this->is_announcement_.value() ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING
 | 
			
		||||
                                                   : media_player::MEDIA_PLAYER_STATE_PLAYING;
 | 
			
		||||
    if (this->is_announcement_) {
 | 
			
		||||
      this->state = media_player::MEDIA_PLAYER_STATE_ANNOUNCING;
 | 
			
		||||
    }
 | 
			
		||||
    this->publish_state();
 | 
			
		||||
  }
 | 
			
		||||
@@ -202,6 +212,7 @@ void I2SAudioMediaPlayer::stop_() {
 | 
			
		||||
  this->high_freq_.stop();
 | 
			
		||||
  this->state = media_player::MEDIA_PLAYER_STATE_IDLE;
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
  this->is_announcement_ = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
media_player::MediaPlayerTraits I2SAudioMediaPlayer::get_traits() {
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer,
 | 
			
		||||
  HighFrequencyLoopRequester high_freq_;
 | 
			
		||||
 | 
			
		||||
  optional<std::string> current_url_{};
 | 
			
		||||
  optional<bool> is_announcement_{};
 | 
			
		||||
  bool is_announcement_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace i2s_audio
 | 
			
		||||
 
 | 
			
		||||
@@ -38,15 +38,22 @@ void I2SAudioSpeaker::start() {
 | 
			
		||||
    ESP_LOGE(TAG, "Cannot start audio, speaker failed to setup");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->task_created_) {
 | 
			
		||||
    ESP_LOGW(TAG, "Called start while task has been already created.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->state_ = speaker::STATE_STARTING;
 | 
			
		||||
}
 | 
			
		||||
void I2SAudioSpeaker::start_() {
 | 
			
		||||
  if (this->task_created_) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->parent_->try_lock()) {
 | 
			
		||||
    return;  // Waiting for another i2s component to return lock
 | 
			
		||||
  }
 | 
			
		||||
  this->state_ = speaker::STATE_RUNNING;
 | 
			
		||||
 | 
			
		||||
  xTaskCreate(I2SAudioSpeaker::player_task, "speaker_task", 8192, (void *) this, 1, &this->player_task_handle_);
 | 
			
		||||
  this->task_created_ = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void I2SAudioSpeaker::player_task(void *params) {
 | 
			
		||||
@@ -131,7 +138,16 @@ void I2SAudioSpeaker::player_task(void *params) {
 | 
			
		||||
                                (10 / portTICK_PERIOD_MS));
 | 
			
		||||
      if (err != ESP_OK) {
 | 
			
		||||
        event = {.type = TaskEventType::WARNING, .err = err};
 | 
			
		||||
        xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
			
		||||
        if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
 | 
			
		||||
          ESP_LOGW(TAG, "Failed to send WARNING event");
 | 
			
		||||
        }
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (bytes_written != sizeof(sample)) {
 | 
			
		||||
        event = {.type = TaskEventType::WARNING, .err = ESP_FAIL};
 | 
			
		||||
        if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
 | 
			
		||||
          ESP_LOGW(TAG, "Failed to send WARNING event");
 | 
			
		||||
        }
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      remaining--;
 | 
			
		||||
@@ -139,18 +155,25 @@ void I2SAudioSpeaker::player_task(void *params) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    event.type = TaskEventType::PLAYING;
 | 
			
		||||
    xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
			
		||||
    event.err = current;
 | 
			
		||||
    if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
 | 
			
		||||
      ESP_LOGW(TAG, "Failed to send PLAYING event");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  event.type = TaskEventType::STOPPING;
 | 
			
		||||
  if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
 | 
			
		||||
    ESP_LOGW(TAG, "Failed to send STOPPING event");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  i2s_zero_dma_buffer(this_speaker->parent_->get_port());
 | 
			
		||||
 | 
			
		||||
  event.type = TaskEventType::STOPPING;
 | 
			
		||||
  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
			
		||||
 | 
			
		||||
  i2s_driver_uninstall(this_speaker->parent_->get_port());
 | 
			
		||||
 | 
			
		||||
  event.type = TaskEventType::STOPPED;
 | 
			
		||||
  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
			
		||||
  if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
 | 
			
		||||
    ESP_LOGW(TAG, "Failed to send STOPPED event");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  while (true) {
 | 
			
		||||
    delay(10);
 | 
			
		||||
@@ -181,6 +204,7 @@ void I2SAudioSpeaker::watch_() {
 | 
			
		||||
        break;
 | 
			
		||||
      case TaskEventType::STARTED:
 | 
			
		||||
        ESP_LOGD(TAG, "Started I2S Audio Speaker");
 | 
			
		||||
        this->state_ = speaker::STATE_RUNNING;
 | 
			
		||||
        break;
 | 
			
		||||
      case TaskEventType::STOPPING:
 | 
			
		||||
        ESP_LOGD(TAG, "Stopping I2S Audio Speaker");
 | 
			
		||||
@@ -191,6 +215,7 @@ void I2SAudioSpeaker::watch_() {
 | 
			
		||||
      case TaskEventType::STOPPED:
 | 
			
		||||
        this->state_ = speaker::STATE_STOPPED;
 | 
			
		||||
        vTaskDelete(this->player_task_handle_);
 | 
			
		||||
        this->task_created_ = false;
 | 
			
		||||
        this->player_task_handle_ = nullptr;
 | 
			
		||||
        this->parent_->unlock();
 | 
			
		||||
        xQueueReset(this->buffer_queue_);
 | 
			
		||||
@@ -208,7 +233,6 @@ void I2SAudioSpeaker::loop() {
 | 
			
		||||
  switch (this->state_) {
 | 
			
		||||
    case speaker::STATE_STARTING:
 | 
			
		||||
      this->start_();
 | 
			
		||||
      break;
 | 
			
		||||
    case speaker::STATE_RUNNING:
 | 
			
		||||
    case speaker::STATE_STOPPING:
 | 
			
		||||
      this->watch_();
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,6 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void start_();
 | 
			
		||||
  // void stop_();
 | 
			
		||||
  void watch_();
 | 
			
		||||
 | 
			
		||||
  static void player_task(void *params);
 | 
			
		||||
@@ -70,6 +69,7 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud
 | 
			
		||||
  QueueHandle_t event_queue_;
 | 
			
		||||
 | 
			
		||||
  uint8_t dout_pin_{0};
 | 
			
		||||
  bool task_created_{false};
 | 
			
		||||
 | 
			
		||||
#if SOC_I2S_SUPPORTS_DAC
 | 
			
		||||
  i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
 | 
			
		||||
 
 | 
			
		||||
@@ -69,6 +69,7 @@ MODELS = {
 | 
			
		||||
    "ILI9486": ili9xxx_ns.class_("ILI9XXXILI9486", ILI9XXXDisplay),
 | 
			
		||||
    "ILI9488": ili9xxx_ns.class_("ILI9XXXILI9488", ILI9XXXDisplay),
 | 
			
		||||
    "ILI9488_A": ili9xxx_ns.class_("ILI9XXXILI9488A", ILI9XXXDisplay),
 | 
			
		||||
    "ST7735": ili9xxx_ns.class_("ILI9XXXST7735", ILI9XXXDisplay),
 | 
			
		||||
    "ST7796": ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay),
 | 
			
		||||
    "ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay),
 | 
			
		||||
    "S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
 | 
			
		||||
@@ -134,6 +135,7 @@ def _validate(config):
 | 
			
		||||
        "ILI9341",
 | 
			
		||||
        "ILI9342",
 | 
			
		||||
        "ST7789V",
 | 
			
		||||
        "ST7735",
 | 
			
		||||
    ]:
 | 
			
		||||
        raise cv.Invalid("Selected model can't run on ESP8266.")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -70,6 +70,7 @@ static const uint8_t ILI9XXX_PWCTR2 = 0xC1;
 | 
			
		||||
static const uint8_t ILI9XXX_PWCTR3 = 0xC2;
 | 
			
		||||
static const uint8_t ILI9XXX_PWCTR4 = 0xC3;
 | 
			
		||||
static const uint8_t ILI9XXX_PWCTR5 = 0xC4;
 | 
			
		||||
static const uint8_t ILI9XXX_PWCTR6 = 0xF6;
 | 
			
		||||
static const uint8_t ILI9XXX_VMCTR1 = 0xC5;
 | 
			
		||||
static const uint8_t ILI9XXX_IFCTR = 0xC6;
 | 
			
		||||
static const uint8_t ILI9XXX_VMCTR2 = 0xC7;
 | 
			
		||||
@@ -91,6 +92,7 @@ static const uint8_t ILI9XXX_GMCTRN1 = 0xE1;
 | 
			
		||||
 | 
			
		||||
static const uint8_t ILI9XXX_CSCON = 0xF0;
 | 
			
		||||
static const uint8_t ILI9XXX_ADJCTL3 = 0xF7;
 | 
			
		||||
static const uint8_t ILI9XXX_DELAY = 0xFF;  // followed by one byte of delay time in ms
 | 
			
		||||
 | 
			
		||||
}  // namespace ili9xxx
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -34,8 +34,8 @@ void ILI9XXXDisplay::setup() {
 | 
			
		||||
  ESP_LOGD(TAG, "Setting up ILI9xxx");
 | 
			
		||||
 | 
			
		||||
  this->setup_pins_();
 | 
			
		||||
  this->init_lcd_(this->init_sequence_);
 | 
			
		||||
  this->init_lcd_(this->extra_init_sequence_.data());
 | 
			
		||||
  this->init_lcd(this->init_sequence_);
 | 
			
		||||
  this->init_lcd(this->extra_init_sequence_.data());
 | 
			
		||||
  switch (this->pixel_mode_) {
 | 
			
		||||
    case PIXEL_MODE_16:
 | 
			
		||||
      if (this->is_18bitdisplay_) {
 | 
			
		||||
@@ -405,7 +405,29 @@ void ILI9XXXDisplay::reset_() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) {
 | 
			
		||||
void ILI9XXXDisplay::init_lcd(const uint8_t *addr) {
 | 
			
		||||
  if (addr == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
  uint8_t cmd, x, num_args;
 | 
			
		||||
  while ((cmd = *addr++) != 0) {
 | 
			
		||||
    x = *addr++;
 | 
			
		||||
    if (cmd == ILI9XXX_DELAY) {
 | 
			
		||||
      ESP_LOGD(TAG, "Delay %dms", x);
 | 
			
		||||
      delay(x);
 | 
			
		||||
    } else {
 | 
			
		||||
      num_args = x & 0x7F;
 | 
			
		||||
      ESP_LOGD(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr);
 | 
			
		||||
      this->send_command(cmd, addr, num_args);
 | 
			
		||||
      addr += num_args;
 | 
			
		||||
      if (x & 0x80) {
 | 
			
		||||
        ESP_LOGD(TAG, "Delay 150ms");
 | 
			
		||||
        delay(150);  // NOLINT
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ILI9XXXGC9A01A::init_lcd(const uint8_t *addr) {
 | 
			
		||||
  if (addr == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
  uint8_t cmd, x, num_args;
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,6 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
 | 
			
		||||
    while ((cmd = *addr++) != 0) {
 | 
			
		||||
      num_args = *addr++ & 0x7F;
 | 
			
		||||
      bits = *addr;
 | 
			
		||||
      esph_log_d(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits);
 | 
			
		||||
      switch (cmd) {
 | 
			
		||||
        case ILI9XXX_MADCTL: {
 | 
			
		||||
          this->swap_xy_ = (bits & MADCTL_MV) != 0;
 | 
			
		||||
@@ -51,6 +50,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case ILI9XXX_DELAY:
 | 
			
		||||
          continue;  // no args to skip
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
@@ -107,7 +109,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
 | 
			
		||||
 | 
			
		||||
  virtual void set_madctl();
 | 
			
		||||
  void display_();
 | 
			
		||||
  void init_lcd_(const uint8_t *addr);
 | 
			
		||||
  virtual void init_lcd(const uint8_t *addr);
 | 
			
		||||
  void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
 | 
			
		||||
  void reset_();
 | 
			
		||||
 | 
			
		||||
@@ -267,6 +269,13 @@ class ILI9XXXS3BoxLite : public ILI9XXXDisplay {
 | 
			
		||||
class ILI9XXXGC9A01A : public ILI9XXXDisplay {
 | 
			
		||||
 public:
 | 
			
		||||
  ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {}
 | 
			
		||||
  void init_lcd(const uint8_t *addr) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//-----------   ILI9XXX_24_TFT display --------------
 | 
			
		||||
class ILI9XXXST7735 : public ILI9XXXDisplay {
 | 
			
		||||
 public:
 | 
			
		||||
  ILI9XXXST7735() : ILI9XXXDisplay(INITCMD_ST7735, 128, 160, false) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ili9xxx
 | 
			
		||||
 
 | 
			
		||||
@@ -370,6 +370,57 @@ static const uint8_t PROGMEM INITCMD_GC9A01A[] = {
 | 
			
		||||
  0x00                  // End of list
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const uint8_t PROGMEM INITCMD_ST7735[] = {
 | 
			
		||||
    ILI9XXX_SWRESET, 0,         // Soft reset, then delay 10ms
 | 
			
		||||
    ILI9XXX_DELAY, 10,
 | 
			
		||||
    ILI9XXX_SLPOUT  , 0,                // Exit Sleep, delay
 | 
			
		||||
    ILI9XXX_DELAY, 10,
 | 
			
		||||
    ILI9XXX_PIXFMT  , 1, 0x05,
 | 
			
		||||
    ILI9XXX_FRMCTR1, 3, //  4: Frame rate control, 3 args + delay:
 | 
			
		||||
    0x01, 0x2C, 0x2D,             //     Rate = fosc/(1x2+40) * (LINE+2C+2D)
 | 
			
		||||
    ILI9XXX_FRMCTR2, 3,              //  4: Framerate ctrl - idle mode, 3 args:
 | 
			
		||||
    0x01, 0x2C, 0x2D,             //     Rate = fosc/(1x2+40) * (LINE+2C+2D)
 | 
			
		||||
    ILI9XXX_FRMCTR3, 6,              //  5: Framerate - partial mode, 6 args:
 | 
			
		||||
    0x01, 0x2C, 0x2D,             //     Dot inversion mode
 | 
			
		||||
    0x01, 0x2C, 0x2D,             //     Line inversion mode
 | 
			
		||||
 | 
			
		||||
    ILI9XXX_INVCTR, 1,              //  7: Display inversion control, 1 arg:
 | 
			
		||||
    0x7,                          //     Line inversion
 | 
			
		||||
    ILI9XXX_PWCTR1,  3,              //  7: Power control, 3 args, no delay:
 | 
			
		||||
    0xA2,
 | 
			
		||||
    0x02,                         //     -4.6V
 | 
			
		||||
    0x84,                         //     AUTO mode
 | 
			
		||||
    ILI9XXX_PWCTR2,  1,              //  8: Power control, 1 arg, no delay:
 | 
			
		||||
    0xC5,                         //     VGH25=2.4C VGSEL=-10 VGH=3 * AVDD
 | 
			
		||||
    ILI9XXX_PWCTR3,  2,              //  9: Power control, 2 args, no delay:
 | 
			
		||||
    0x0A,                         //     Opamp current small
 | 
			
		||||
    0x00,                         //     Boost frequency
 | 
			
		||||
    ILI9XXX_PWCTR4,  2,              // 10: Power control, 2 args, no delay:
 | 
			
		||||
    0x8A,                         //     BCLK/2,
 | 
			
		||||
    0x2A,                         //     opamp current small & medium low
 | 
			
		||||
    ILI9XXX_PWCTR5,  2,              // 11: Power control, 2 args, no delay:
 | 
			
		||||
    0x8A, 0xEE,
 | 
			
		||||
 | 
			
		||||
    ILI9XXX_VMCTR1, 1, // 11: Power control, 2 args + delay:
 | 
			
		||||
    0x0E,
 | 
			
		||||
    ILI9XXX_GMCTRP1, 16,              // 13: Gamma Adjustments (pos. polarity), 16 args + delay:
 | 
			
		||||
    0x02, 0x1c, 0x07, 0x12,       //     (Not entirely necessary, but provides
 | 
			
		||||
    0x37, 0x32, 0x29, 0x2d,       //      accurate colors)
 | 
			
		||||
    0x29, 0x25, 0x2B, 0x39,
 | 
			
		||||
    0x00, 0x01, 0x03, 0x10,
 | 
			
		||||
    ILI9XXX_GMCTRN1, 16, // 14: Gamma Adjustments (neg. polarity), 16 args + delay:
 | 
			
		||||
    0x03, 0x1d, 0x07, 0x06,       //     (Not entirely necessary, but provides
 | 
			
		||||
    0x2E, 0x2C, 0x29, 0x2D,       //      accurate colors)
 | 
			
		||||
    0x2E, 0x2E, 0x37, 0x3F,
 | 
			
		||||
    0x00, 0x00, 0x02, 0x10,
 | 
			
		||||
    ILI9XXX_MADCTL  , 1, 0x00,             // Memory Access Control, BGR
 | 
			
		||||
    ILI9XXX_NORON  , 0,
 | 
			
		||||
    ILI9XXX_DELAY, 10,
 | 
			
		||||
    ILI9XXX_DISPON  , 0,                // Display on
 | 
			
		||||
    ILI9XXX_DELAY, 10,
 | 
			
		||||
    00,   // endo of list
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// clang-format on
 | 
			
		||||
}  // namespace ili9xxx
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,6 @@ import re
 | 
			
		||||
import requests
 | 
			
		||||
from magic import Magic
 | 
			
		||||
 | 
			
		||||
from PIL import Image
 | 
			
		||||
 | 
			
		||||
from esphome import core
 | 
			
		||||
from esphome.components import font
 | 
			
		||||
from esphome import external_files
 | 
			
		||||
@@ -68,7 +66,7 @@ def _compute_local_icon_path(value: dict) -> Path:
 | 
			
		||||
    return base_dir / f"{value[CONF_ICON]}.svg"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _compute_local_image_path(value: dict) -> Path:
 | 
			
		||||
def compute_local_image_path(value: dict) -> Path:
 | 
			
		||||
    url = value[CONF_URL]
 | 
			
		||||
    h = hashlib.new("sha256")
 | 
			
		||||
    h.update(url.encode())
 | 
			
		||||
@@ -117,7 +115,7 @@ def download_mdi(value):
 | 
			
		||||
 | 
			
		||||
def download_image(value):
 | 
			
		||||
    url = value[CONF_URL]
 | 
			
		||||
    path = _compute_local_image_path(value)
 | 
			
		||||
    path = compute_local_image_path(value)
 | 
			
		||||
 | 
			
		||||
    download_content(url, path)
 | 
			
		||||
 | 
			
		||||
@@ -267,6 +265,9 @@ CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def load_svg_image(file: bytes, resize: tuple[int, int]):
 | 
			
		||||
    # Local import only to allow "validate_pillow_installed" to run *before* importing it
 | 
			
		||||
    from PIL import Image
 | 
			
		||||
 | 
			
		||||
    # This import is only needed in case of SVG images; adding it
 | 
			
		||||
    # to the top would force configurations not using SVG to also have it
 | 
			
		||||
    # installed for no reason.
 | 
			
		||||
@@ -286,6 +287,9 @@ def load_svg_image(file: bytes, resize: tuple[int, int]):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    # Local import only to allow "validate_pillow_installed" to run *before* importing it
 | 
			
		||||
    from PIL import Image
 | 
			
		||||
 | 
			
		||||
    conf_file = config[CONF_FILE]
 | 
			
		||||
 | 
			
		||||
    if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
 | 
			
		||||
@@ -295,7 +299,7 @@ async def to_code(config):
 | 
			
		||||
        path = _compute_local_icon_path(conf_file).as_posix()
 | 
			
		||||
 | 
			
		||||
    elif conf_file[CONF_SOURCE] == SOURCE_WEB:
 | 
			
		||||
        path = _compute_local_image_path(conf_file).as_posix()
 | 
			
		||||
        path = compute_local_image_path(conf_file).as_posix()
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        with open(path, "rb") as f:
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ std::string build_json(const json_build_t &f) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void parse_json(const std::string &data, const json_parse_t &f) {
 | 
			
		||||
bool parse_json(const std::string &data, const json_parse_t &f) {
 | 
			
		||||
  // Here we are allocating 1.5 times the data size,
 | 
			
		||||
  // with the heap size minus 2kb to be safe if less than that
 | 
			
		||||
  // as we can not have a true dynamic sized document.
 | 
			
		||||
@@ -76,14 +76,13 @@ void parse_json(const std::string &data, const json_parse_t &f) {
 | 
			
		||||
#elif defined(USE_LIBRETINY)
 | 
			
		||||
  const size_t free_heap = lt_heap_get_free();
 | 
			
		||||
#endif
 | 
			
		||||
  bool pass = false;
 | 
			
		||||
  size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5));
 | 
			
		||||
  do {
 | 
			
		||||
  while (true) {
 | 
			
		||||
    DynamicJsonDocument json_document(request_size);
 | 
			
		||||
    if (json_document.capacity() == 0) {
 | 
			
		||||
      ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size,
 | 
			
		||||
               free_heap);
 | 
			
		||||
      return;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    DeserializationError err = deserializeJson(json_document, data);
 | 
			
		||||
    json_document.shrinkToFit();
 | 
			
		||||
@@ -91,21 +90,21 @@ void parse_json(const std::string &data, const json_parse_t &f) {
 | 
			
		||||
    JsonObject root = json_document.as<JsonObject>();
 | 
			
		||||
 | 
			
		||||
    if (err == DeserializationError::Ok) {
 | 
			
		||||
      pass = true;
 | 
			
		||||
      f(root);
 | 
			
		||||
      return f(root);
 | 
			
		||||
    } else if (err == DeserializationError::NoMemory) {
 | 
			
		||||
      if (request_size * 2 >= free_heap) {
 | 
			
		||||
        ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller");
 | 
			
		||||
        return;
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      ESP_LOGV(TAG, "Increasing memory allocation.");
 | 
			
		||||
      request_size *= 2;
 | 
			
		||||
      continue;
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGE(TAG, "JSON parse error: %s", err.c_str());
 | 
			
		||||
      return;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  } while (!pass);
 | 
			
		||||
  };
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace json
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ namespace esphome {
 | 
			
		||||
namespace json {
 | 
			
		||||
 | 
			
		||||
/// Callback function typedef for parsing JsonObjects.
 | 
			
		||||
using json_parse_t = std::function<void(JsonObject)>;
 | 
			
		||||
using json_parse_t = std::function<bool(JsonObject)>;
 | 
			
		||||
 | 
			
		||||
/// Callback function typedef for building JsonObjects.
 | 
			
		||||
using json_build_t = std::function<void(JsonObject)>;
 | 
			
		||||
@@ -23,7 +23,7 @@ using json_build_t = std::function<void(JsonObject)>;
 | 
			
		||||
std::string build_json(const json_build_t &f);
 | 
			
		||||
 | 
			
		||||
/// Parse a JSON string and run the provided json parse function if it's valid.
 | 
			
		||||
void parse_json(const std::string &data, const json_parse_t &f);
 | 
			
		||||
bool parse_json(const std::string &data, const json_parse_t &f);
 | 
			
		||||
 | 
			
		||||
}  // namespace json
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -112,11 +112,18 @@ HARDWARE_UART_TO_UART_SELECTION = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
HARDWARE_UART_TO_SERIAL = {
 | 
			
		||||
    PLATFORM_ESP8266: {
 | 
			
		||||
        UART0: cg.global_ns.Serial,
 | 
			
		||||
        UART0_SWAP: cg.global_ns.Serial,
 | 
			
		||||
        UART1: cg.global_ns.Serial1,
 | 
			
		||||
        UART2: cg.global_ns.Serial2,
 | 
			
		||||
        DEFAULT: cg.global_ns.Serial,
 | 
			
		||||
    },
 | 
			
		||||
    PLATFORM_RP2040: {
 | 
			
		||||
        UART0: cg.global_ns.Serial1,
 | 
			
		||||
        UART1: cg.global_ns.Serial2,
 | 
			
		||||
        USB_CDC: cg.global_ns.Serial,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
 | 
			
		||||
@@ -244,8 +251,14 @@ async def to_code(config):
 | 
			
		||||
    is_at_least_very_verbose = this_severity >= very_verbose_severity
 | 
			
		||||
    has_serial_logging = baud_rate != 0
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp8266 and has_serial_logging and is_at_least_verbose:
 | 
			
		||||
        debug_serial_port = HARDWARE_UART_TO_SERIAL[config.get(CONF_HARDWARE_UART)]
 | 
			
		||||
    if (
 | 
			
		||||
        (CORE.is_esp8266 or CORE.is_rp2040)
 | 
			
		||||
        and has_serial_logging
 | 
			
		||||
        and is_at_least_verbose
 | 
			
		||||
    ):
 | 
			
		||||
        debug_serial_port = HARDWARE_UART_TO_SERIAL[CORE.target_platform][
 | 
			
		||||
            config.get(CONF_HARDWARE_UART)
 | 
			
		||||
        ]
 | 
			
		||||
        cg.add_build_flag(f"-DDEBUG_ESP_PORT={debug_serial_port}")
 | 
			
		||||
        cg.add_build_flag("-DLWIP_DEBUG")
 | 
			
		||||
        DEBUG_COMPONENTS = {
 | 
			
		||||
 
 | 
			
		||||
@@ -312,6 +312,7 @@ async def to_code(config):
 | 
			
		||||
    esp32.add_idf_component(
 | 
			
		||||
        name="esp-tflite-micro",
 | 
			
		||||
        repo="https://github.com/espressif/esp-tflite-micro",
 | 
			
		||||
        ref="v1.3.1",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    cg.add_build_flag("-DTF_LITE_STATIC_MEMORY")
 | 
			
		||||
 
 | 
			
		||||
@@ -126,6 +126,7 @@ MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
 | 
			
		||||
MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent)
 | 
			
		||||
MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent)
 | 
			
		||||
MQTTEventComponent = mqtt_ns.class_("MQTTEventComponent", MQTTComponent)
 | 
			
		||||
MQTTUpdateComponent = mqtt_ns.class_("MQTTUpdateComponent", MQTTComponent)
 | 
			
		||||
MQTTValveComponent = mqtt_ns.class_("MQTTValveComponent", MQTTComponent)
 | 
			
		||||
 | 
			
		||||
MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator")
 | 
			
		||||
 
 | 
			
		||||
@@ -410,7 +410,10 @@ void MQTTClientComponent::subscribe(const std::string &topic, mqtt_callback_t ca
 | 
			
		||||
 | 
			
		||||
void MQTTClientComponent::subscribe_json(const std::string &topic, const mqtt_json_callback_t &callback, uint8_t qos) {
 | 
			
		||||
  auto f = [callback](const std::string &topic, const std::string &payload) {
 | 
			
		||||
    json::parse_json(payload, [topic, callback](JsonObject root) { callback(topic, root); });
 | 
			
		||||
    json::parse_json(payload, [topic, callback](JsonObject root) -> bool {
 | 
			
		||||
      callback(topic, root);
 | 
			
		||||
      return true;
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
  MQTTSubscription subscription{
 | 
			
		||||
      .topic = topic,
 | 
			
		||||
 
 | 
			
		||||
@@ -137,6 +137,7 @@ constexpr const char *const MQTT_PAYLOAD_CLOSE = "pl_cls";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_DISARM = "pl_disarm";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "pl_hi_spd";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_HOME = "pl_home";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_INSTALL = "pl_inst";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_LOCATE = "pl_loc";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "pl_lo_spd";
 | 
			
		||||
@@ -396,6 +397,7 @@ constexpr const char *const MQTT_PAYLOAD_CLOSE = "payload_close";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_DISARM = "payload_disarm";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "payload_high_speed";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_HOME = "payload_home";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_INSTALL = "payload_install";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_LOCATE = "payload_locate";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock";
 | 
			
		||||
constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "payload_low_speed";
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,12 @@
 | 
			
		||||
#include "mqtt_const.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_MQTT
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mqtt {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "mqtt.datetime.time";
 | 
			
		||||
static const char *const TAG = "mqtt.datetime.datetime";
 | 
			
		||||
 | 
			
		||||
using namespace esphome::datetime;
 | 
			
		||||
 | 
			
		||||
@@ -80,5 +80,5 @@ bool MQTTDateTimeComponent::publish_state(uint16_t year, uint8_t month, uint8_t
 | 
			
		||||
}  // namespace mqtt
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_DATETIME_TIME
 | 
			
		||||
#endif  // USE_DATETIME_DATETIME
 | 
			
		||||
#endif  // USE_MQTT
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_MQTT
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/datetime/datetime_entity.h"
 | 
			
		||||
#include "mqtt_component.h"
 | 
			
		||||
@@ -17,7 +17,7 @@ class MQTTDateTimeComponent : public mqtt::MQTTComponent {
 | 
			
		||||
   *
 | 
			
		||||
   * @param time The time entity.
 | 
			
		||||
   */
 | 
			
		||||
  explicit MQTTDateTimeComponent(datetime::DateTimeEntity *time);
 | 
			
		||||
  explicit MQTTDateTimeComponent(datetime::DateTimeEntity *datetime);
 | 
			
		||||
 | 
			
		||||
  // ========== INTERNAL METHODS ==========
 | 
			
		||||
  // (In most use cases you won't need these)
 | 
			
		||||
@@ -41,5 +41,5 @@ class MQTTDateTimeComponent : public mqtt::MQTTComponent {
 | 
			
		||||
}  // namespace mqtt
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_DATETIME_DATE
 | 
			
		||||
#endif  // USE_DATETIME_DATETIME
 | 
			
		||||
#endif  // USE_MQTT
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										62
									
								
								esphome/components/mqtt/mqtt_update.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								esphome/components/mqtt/mqtt_update.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
#include "mqtt_update.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include "mqtt_const.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_MQTT
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mqtt {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "mqtt.update";
 | 
			
		||||
 | 
			
		||||
using namespace esphome::update;
 | 
			
		||||
 | 
			
		||||
MQTTUpdateComponent::MQTTUpdateComponent(UpdateEntity *update) : update_(update) {}
 | 
			
		||||
 | 
			
		||||
void MQTTUpdateComponent::setup() {
 | 
			
		||||
  this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
 | 
			
		||||
    if (payload == "INSTALL") {
 | 
			
		||||
      this->update_->perform();
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGW(TAG, "'%s': Received unknown update payload: %s", this->friendly_name().c_str(), payload.c_str());
 | 
			
		||||
      this->status_momentary_warning("state", 5000);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  this->update_->add_on_state_callback([this]() { this->defer("send", [this]() { this->publish_state(); }); });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MQTTUpdateComponent::publish_state() {
 | 
			
		||||
  return this->publish_json(this->get_state_topic_(), [this](JsonObject root) {
 | 
			
		||||
    root["installed_version"] = this->update_->update_info.current_version;
 | 
			
		||||
    root["latest_version"] = this->update_->update_info.latest_version;
 | 
			
		||||
    root["title"] = this->update_->update_info.title;
 | 
			
		||||
    if (!this->update_->update_info.summary.empty())
 | 
			
		||||
      root["release_summary"] = this->update_->update_info.summary;
 | 
			
		||||
    if (!this->update_->update_info.release_url.empty())
 | 
			
		||||
      root["release_url"] = this->update_->update_info.release_url;
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MQTTUpdateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
 | 
			
		||||
  root["schema"] = "json";
 | 
			
		||||
  root[MQTT_PAYLOAD_INSTALL] = "INSTALL";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MQTTUpdateComponent::send_initial_state() { return this->publish_state(); }
 | 
			
		||||
 | 
			
		||||
void MQTTUpdateComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "MQTT Update '%s': ", this->update_->get_name().c_str());
 | 
			
		||||
  LOG_MQTT_COMPONENT(true, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string MQTTUpdateComponent::component_type() const { return "update"; }
 | 
			
		||||
const EntityBase *MQTTUpdateComponent::get_entity() const { return this->update_; }
 | 
			
		||||
 | 
			
		||||
}  // namespace mqtt
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_UPDATE
 | 
			
		||||
#endif  // USE_MQTT
 | 
			
		||||
							
								
								
									
										41
									
								
								esphome/components/mqtt/mqtt_update.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								esphome/components/mqtt/mqtt_update.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_MQTT
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/update/update_entity.h"
 | 
			
		||||
#include "mqtt_component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mqtt {
 | 
			
		||||
 | 
			
		||||
class MQTTUpdateComponent : public mqtt::MQTTComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MQTTUpdateComponent(update::UpdateEntity *update);
 | 
			
		||||
 | 
			
		||||
  // ========== INTERNAL METHODS ==========
 | 
			
		||||
  // (In most use cases you won't need these)
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
 | 
			
		||||
 | 
			
		||||
  bool send_initial_state() override;
 | 
			
		||||
 | 
			
		||||
  bool publish_state();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  /// "update" component type.
 | 
			
		||||
  std::string component_type() const override;
 | 
			
		||||
  const EntityBase *get_entity() const override;
 | 
			
		||||
 | 
			
		||||
  update::UpdateEntity *update_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace mqtt
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_UPDATE
 | 
			
		||||
#endif  // USE_MQTT
 | 
			
		||||
@@ -19,7 +19,12 @@ IPAddress = network_ns.class_("IPAddress")
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.SplitDefault(CONF_ENABLE_IPV6): cv.All(
 | 
			
		||||
        cv.SplitDefault(
 | 
			
		||||
            CONF_ENABLE_IPV6,
 | 
			
		||||
            esp8266=False,
 | 
			
		||||
            esp32=False,
 | 
			
		||||
            rp2040=False,
 | 
			
		||||
        ): cv.All(
 | 
			
		||||
            cv.boolean, cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040])
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int,
 | 
			
		||||
@@ -28,18 +33,17 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    if CONF_ENABLE_IPV6 in config:
 | 
			
		||||
        cg.add_define("USE_NETWORK_IPV6", config[CONF_ENABLE_IPV6])
 | 
			
		||||
    if (enable_ipv6 := config.get(CONF_ENABLE_IPV6, None)) is not None:
 | 
			
		||||
        cg.add_define("USE_NETWORK_IPV6", enable_ipv6)
 | 
			
		||||
        if enable_ipv6:
 | 
			
		||||
            cg.add_define(
 | 
			
		||||
                "USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT]
 | 
			
		||||
            )
 | 
			
		||||
        if CORE.using_esp_idf:
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6])
 | 
			
		||||
            add_idf_sdkconfig_option(
 | 
			
		||||
                "CONFIG_LWIP_IPV6_AUTOCONFIG", config[CONF_ENABLE_IPV6]
 | 
			
		||||
            )
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6)
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6)
 | 
			
		||||
        else:
 | 
			
		||||
            if config[CONF_ENABLE_IPV6]:
 | 
			
		||||
            if enable_ipv6:
 | 
			
		||||
                cg.add_build_flag("-DCONFIG_LWIP_IPV6")
 | 
			
		||||
                cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG")
 | 
			
		||||
                if CORE.is_rp2040:
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_CARBON_MONOXIDE,
 | 
			
		||||
    DEVICE_CLASS_CONDUCTIVITY,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_DATA_RATE,
 | 
			
		||||
    DEVICE_CLASS_DATA_SIZE,
 | 
			
		||||
@@ -82,6 +83,7 @@ DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_CARBON_MONOXIDE,
 | 
			
		||||
    DEVICE_CLASS_CONDUCTIVITY,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_DATA_RATE,
 | 
			
		||||
    DEVICE_CLASS_DATA_SIZE,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								esphome/components/one_wire/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								esphome/components/one_wire/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ADDRESS
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@ssieb"]
 | 
			
		||||
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
CONF_ONE_WIRE_ID = "one_wire_id"
 | 
			
		||||
 | 
			
		||||
one_wire_ns = cg.esphome_ns.namespace("one_wire")
 | 
			
		||||
OneWireBus = one_wire_ns.class_("OneWireBus")
 | 
			
		||||
OneWireDevice = one_wire_ns.class_("OneWireDevice")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def one_wire_device_schema():
 | 
			
		||||
    """Create a schema for a 1-wire device.
 | 
			
		||||
 | 
			
		||||
    :return: The 1-wire device schema, `extend` this in your config schema.
 | 
			
		||||
    """
 | 
			
		||||
    schema = cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(CONF_ONE_WIRE_ID): cv.use_id(OneWireBus),
 | 
			
		||||
            cv.Optional(CONF_ADDRESS): cv.hex_uint64_t,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    return schema
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def register_one_wire_device(var, config):
 | 
			
		||||
    """Register an 1-wire device with the given config.
 | 
			
		||||
 | 
			
		||||
    Sets the 1-wire bus to use and the 1-wire address.
 | 
			
		||||
 | 
			
		||||
    This is a coroutine, you need to await it with a 'yield' expression!
 | 
			
		||||
    """
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ONE_WIRE_ID])
 | 
			
		||||
    cg.add(var.set_one_wire_bus(parent))
 | 
			
		||||
    if (address := config.get(CONF_ADDRESS)) is not None:
 | 
			
		||||
        cg.add(var.set_address(address))
 | 
			
		||||
							
								
								
									
										40
									
								
								esphome/components/one_wire/one_wire.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								esphome/components/one_wire/one_wire.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
#include "one_wire.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace one_wire {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "one_wire";
 | 
			
		||||
 | 
			
		||||
const std::string &OneWireDevice::get_address_name() {
 | 
			
		||||
  if (this->address_name_.empty())
 | 
			
		||||
    this->address_name_ = std::string("0x") + format_hex(this->address_);
 | 
			
		||||
  return this->address_name_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string OneWireDevice::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); }
 | 
			
		||||
 | 
			
		||||
bool OneWireDevice::send_command_(uint8_t cmd) {
 | 
			
		||||
  if (!this->bus_->select(this->address_))
 | 
			
		||||
    return false;
 | 
			
		||||
  this->bus_->write8(cmd);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool OneWireDevice::check_address_() {
 | 
			
		||||
  if (this->address_ != 0)
 | 
			
		||||
    return true;
 | 
			
		||||
  auto devices = this->bus_->get_devices();
 | 
			
		||||
  if (devices.empty()) {
 | 
			
		||||
    ESP_LOGE(TAG, "No devices, can't auto-select address");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (devices.size() > 1) {
 | 
			
		||||
    ESP_LOGE(TAG, "More than one device, can't auto-select address");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  this->address_ = devices[0];
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace one_wire
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										44
									
								
								esphome/components/one_wire/one_wire.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								esphome/components/one_wire/one_wire.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "one_wire_bus.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace one_wire {
 | 
			
		||||
 | 
			
		||||
#define LOG_ONE_WIRE_DEVICE(this) \
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Address: %s (%s)", this->get_address_name().c_str(), \
 | 
			
		||||
                LOG_STR_ARG(this->bus_->get_model_str(this->address_ & 0xff)));
 | 
			
		||||
 | 
			
		||||
class OneWireDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  /// @brief store the address of the device
 | 
			
		||||
  /// @param address of the device
 | 
			
		||||
  void set_address(uint64_t address) { this->address_ = address; }
 | 
			
		||||
 | 
			
		||||
  /// @brief store the pointer to the OneWireBus to use
 | 
			
		||||
  /// @param bus pointer to the OneWireBus object
 | 
			
		||||
  void set_one_wire_bus(OneWireBus *bus) { this->bus_ = bus; }
 | 
			
		||||
 | 
			
		||||
  /// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
 | 
			
		||||
  const std::string &get_address_name();
 | 
			
		||||
 | 
			
		||||
  std::string unique_id();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint64_t address_{0};
 | 
			
		||||
  OneWireBus *bus_{nullptr};  ///< pointer to OneWireBus instance
 | 
			
		||||
  std::string address_name_;
 | 
			
		||||
 | 
			
		||||
  /// @brief find an address if necessary
 | 
			
		||||
  /// should be called from setup
 | 
			
		||||
  bool check_address_();
 | 
			
		||||
 | 
			
		||||
  /// @brief send command on the bus
 | 
			
		||||
  /// @param cmd command to send
 | 
			
		||||
  bool send_command_(uint8_t cmd);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace one_wire
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										88
									
								
								esphome/components/one_wire/one_wire_bus.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								esphome/components/one_wire/one_wire_bus.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
#include "one_wire_bus.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace one_wire {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "one_wire";
 | 
			
		||||
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS1822 = 0x22;
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS18B20 = 0x28;
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS1825 = 0x3B;
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS28EA00 = 0x42;
 | 
			
		||||
 | 
			
		||||
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
 | 
			
		||||
const uint8_t ONE_WIRE_ROM_SEARCH = 0xF0;
 | 
			
		||||
 | 
			
		||||
const std::vector<uint64_t> &OneWireBus::get_devices() { return this->devices_; }
 | 
			
		||||
 | 
			
		||||
bool IRAM_ATTR OneWireBus::select(uint64_t address) {
 | 
			
		||||
  if (!this->reset())
 | 
			
		||||
    return false;
 | 
			
		||||
  this->write8(ONE_WIRE_ROM_SELECT);
 | 
			
		||||
  this->write64(address);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OneWireBus::search() {
 | 
			
		||||
  this->devices_.clear();
 | 
			
		||||
 | 
			
		||||
  this->reset_search();
 | 
			
		||||
  uint64_t address;
 | 
			
		||||
  while (true) {
 | 
			
		||||
    {
 | 
			
		||||
      InterruptLock lock;
 | 
			
		||||
      if (!this->reset()) {
 | 
			
		||||
        // Reset failed or no devices present
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this->write8(ONE_WIRE_ROM_SEARCH);
 | 
			
		||||
      address = this->search_int();
 | 
			
		||||
    }
 | 
			
		||||
    if (address == 0)
 | 
			
		||||
      break;
 | 
			
		||||
    auto *address8 = reinterpret_cast<uint8_t *>(&address);
 | 
			
		||||
    if (crc8(address8, 7) != address8[7]) {
 | 
			
		||||
      ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
 | 
			
		||||
    } else {
 | 
			
		||||
      this->devices_.push_back(address);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OneWireBus::skip() {
 | 
			
		||||
  this->write8(0xCC);  // skip ROM
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const LogString *OneWireBus::get_model_str(uint8_t model) {
 | 
			
		||||
  switch (model) {
 | 
			
		||||
    case DALLAS_MODEL_DS18S20:
 | 
			
		||||
      return LOG_STR("DS18S20");
 | 
			
		||||
    case DALLAS_MODEL_DS1822:
 | 
			
		||||
      return LOG_STR("DS1822");
 | 
			
		||||
    case DALLAS_MODEL_DS18B20:
 | 
			
		||||
      return LOG_STR("DS18B20");
 | 
			
		||||
    case DALLAS_MODEL_DS1825:
 | 
			
		||||
      return LOG_STR("DS1825");
 | 
			
		||||
    case DALLAS_MODEL_DS28EA00:
 | 
			
		||||
      return LOG_STR("DS28EA00");
 | 
			
		||||
    default:
 | 
			
		||||
      return LOG_STR("Unknown");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OneWireBus::dump_devices_(const char *tag) {
 | 
			
		||||
  if (this->devices_.empty()) {
 | 
			
		||||
    ESP_LOGW(tag, "  Found no devices!");
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "  Found devices:");
 | 
			
		||||
    for (auto &address : this->devices_) {
 | 
			
		||||
      ESP_LOGCONFIG(tag, "    0x%s (%s)", format_hex(address).c_str(), LOG_STR_ARG(get_model_str(address & 0xff)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace one_wire
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										61
									
								
								esphome/components/one_wire/one_wire_bus.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								esphome/components/one_wire/one_wire_bus.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace one_wire {
 | 
			
		||||
 | 
			
		||||
class OneWireBus {
 | 
			
		||||
 public:
 | 
			
		||||
  /** Reset the bus, should be done before all write operations.
 | 
			
		||||
   *
 | 
			
		||||
   * Takes approximately 1ms.
 | 
			
		||||
   *
 | 
			
		||||
   * @return Whether the operation was successful.
 | 
			
		||||
   */
 | 
			
		||||
  virtual bool reset() = 0;
 | 
			
		||||
 | 
			
		||||
  /// Write a word to the bus. LSB first.
 | 
			
		||||
  virtual void write8(uint8_t val) = 0;
 | 
			
		||||
 | 
			
		||||
  /// Write a 64 bit unsigned integer to the bus. LSB first.
 | 
			
		||||
  virtual void write64(uint64_t val) = 0;
 | 
			
		||||
 | 
			
		||||
  /// Write a command to the bus that addresses all devices by skipping the ROM.
 | 
			
		||||
  void skip();
 | 
			
		||||
 | 
			
		||||
  /// Read an 8 bit word from the bus.
 | 
			
		||||
  virtual uint8_t read8() = 0;
 | 
			
		||||
 | 
			
		||||
  /// Read an 64-bit unsigned integer from the bus.
 | 
			
		||||
  virtual uint64_t read64() = 0;
 | 
			
		||||
 | 
			
		||||
  /// Select a specific address on the bus for the following command.
 | 
			
		||||
  bool select(uint64_t address);
 | 
			
		||||
 | 
			
		||||
  /// Return the list of found devices.
 | 
			
		||||
  const std::vector<uint64_t> &get_devices();
 | 
			
		||||
 | 
			
		||||
  /// Search for 1-Wire devices on the bus.
 | 
			
		||||
  void search();
 | 
			
		||||
 | 
			
		||||
  /// Get the description string for this model.
 | 
			
		||||
  const LogString *get_model_str(uint8_t model);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::vector<uint64_t> devices_;
 | 
			
		||||
 | 
			
		||||
  /// log the found devices
 | 
			
		||||
  void dump_devices_(const char *tag);
 | 
			
		||||
 | 
			
		||||
  /// Reset the device search.
 | 
			
		||||
  virtual void reset_search() = 0;
 | 
			
		||||
 | 
			
		||||
  /// Search for a 1-Wire device on the bus. Returns 0 if all devices have been found.
 | 
			
		||||
  virtual uint64_t search_int() = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace one_wire
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -42,6 +42,14 @@ COLOR_ORDERS = {
 | 
			
		||||
}
 | 
			
		||||
DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_dimension(value):
 | 
			
		||||
    value = cv.positive_int(value)
 | 
			
		||||
    if value % 2 != 0:
 | 
			
		||||
        raise cv.Invalid("Width/height/offset must be divisible by 2")
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    display.FULL_DISPLAY_SCHEMA.extend(
 | 
			
		||||
        cv.Schema(
 | 
			
		||||
@@ -52,10 +60,14 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
                    cv.dimensions,
 | 
			
		||||
                    cv.Schema(
 | 
			
		||||
                        {
 | 
			
		||||
                            cv.Required(CONF_WIDTH): cv.int_,
 | 
			
		||||
                            cv.Required(CONF_HEIGHT): cv.int_,
 | 
			
		||||
                            cv.Optional(CONF_OFFSET_HEIGHT, default=0): cv.int_,
 | 
			
		||||
                            cv.Optional(CONF_OFFSET_WIDTH, default=0): cv.int_,
 | 
			
		||||
                            cv.Required(CONF_WIDTH): validate_dimension,
 | 
			
		||||
                            cv.Required(CONF_HEIGHT): validate_dimension,
 | 
			
		||||
                            cv.Optional(
 | 
			
		||||
                                CONF_OFFSET_HEIGHT, default=0
 | 
			
		||||
                            ): validate_dimension,
 | 
			
		||||
                            cv.Optional(
 | 
			
		||||
                                CONF_OFFSET_WIDTH, default=0
 | 
			
		||||
                            ): validate_dimension,
 | 
			
		||||
                        }
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,23 @@ void QspiAmoLed::setup() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QspiAmoLed::update() {
 | 
			
		||||
  if (!this->setup_complete_) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->do_update_();
 | 
			
		||||
  // Start addresses and widths/heights must be divisible by 2 (CASET/RASET restriction in datasheet)
 | 
			
		||||
  if (this->x_low_ % 2 == 1) {
 | 
			
		||||
    this->x_low_--;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->x_high_ % 2 == 0) {
 | 
			
		||||
    this->x_high_++;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->y_low_ % 2 == 1) {
 | 
			
		||||
    this->y_low_--;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->y_high_ % 2 == 0) {
 | 
			
		||||
    this->y_high_++;
 | 
			
		||||
  }
 | 
			
		||||
  int w = this->x_high_ - this->x_low_ + 1;
 | 
			
		||||
  int h = this->y_high_ - this->y_low_ + 1;
 | 
			
		||||
  this->draw_pixels_at(this->x_low_, this->y_low_, w, h, this->buffer_, this->color_mode_, display::COLOR_BITNESS_565,
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user