mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 09:01:49 +00:00 
			
		
		
		
	Compare commits
	
		
			83 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					5f27757039 | ||
| 
						 | 
					532907219b | ||
| 
						 | 
					eeaba74553 | ||
| 
						 | 
					dd637582a4 | ||
| 
						 | 
					b0d12aeea1 | ||
| 
						 | 
					bdbd813455 | ||
| 
						 | 
					a6fac2b175 | ||
| 
						 | 
					5ce923ea90 | ||
| 
						 | 
					29f0508dc2 | ||
| 
						 | 
					3ffa59f0cd | ||
| 
						 | 
					790d6ef94c | ||
| 
						 | 
					7828f48b9a | ||
| 
						 | 
					9fbb3659a6 | ||
| 
						 | 
					fee446c28a | ||
| 
						 | 
					1d56f0b035 | ||
| 
						 | 
					341fddb9aa | ||
| 
						 | 
					456824669f | ||
| 
						 | 
					62f3039d82 | ||
| 
						 | 
					be4c718859 | ||
| 
						 | 
					c2f9ed7c59 | ||
| 
						 | 
					bfac6607d1 | ||
| 
						 | 
					e43dcded62 | ||
| 
						 | 
					887081fd71 | ||
| 
						 | 
					71ded24fce | ||
| 
						 | 
					1e2a9e8348 | ||
| 
						 | 
					64a3aa7092 | ||
| 
						 | 
					fda8dd4ce3 | ||
| 
						 | 
					1efabd27d8 | ||
| 
						 | 
					caa651e55b | ||
| 
						 | 
					10a6e9b4ee | ||
| 
						 | 
					4b8ec44262 | ||
| 
						 | 
					bd74ed4bc0 | ||
| 
						 | 
					d01f296420 | ||
| 
						 | 
					27112e2ace | ||
| 
						 | 
					837930234f | ||
| 
						 | 
					e19aa3bbe0 | ||
| 
						 | 
					35b5c1ed56 | ||
| 
						 | 
					c9d93ff685 | ||
| 
						 | 
					fa72990a63 | ||
| 
						 | 
					e5afb1c4ea | ||
| 
						 | 
					73ead5f328 | ||
| 
						 | 
					5c57b51378 | ||
| 
						 | 
					e25935ef21 | ||
| 
						 | 
					c7a52c3894 | ||
| 
						 | 
					53a4689ed1 | ||
| 
						 | 
					0a82e6e792 | ||
| 
						 | 
					98855e4123 | ||
| 
						 | 
					6a09d7c49b | ||
| 
						 | 
					46e50ba53f | ||
| 
						 | 
					f1e3ff2ed2 | ||
| 
						 | 
					7787fa8f29 | ||
| 
						 | 
					70902029f8 | ||
| 
						 | 
					4f9a56c884 | ||
| 
						 | 
					3715ba030b | ||
| 
						 | 
					0c93be97a9 | ||
| 
						 | 
					54eb6070fb | ||
| 
						 | 
					4dbf1c521e | ||
| 
						 | 
					a91e6a6bdf | ||
| 
						 | 
					8600620305 | ||
| 
						 | 
					96721f305f | ||
| 
						 | 
					2bf70d7d00 | ||
| 
						 | 
					1d8c170f48 | ||
| 
						 | 
					6009c7edb4 | ||
| 
						 | 
					e3f36c033e | ||
| 
						 | 
					d4eb0f1655 | ||
| 
						 | 
					e20ec00071 | ||
| 
						 | 
					150114d774 | ||
| 
						 | 
					89dfa5ea82 | ||
| 
						 | 
					97aa930ad2 | ||
| 
						 | 
					2a5def10e7 | ||
| 
						 | 
					969834e037 | ||
| 
						 | 
					d73a44c504 | ||
| 
						 | 
					8aec092ab6 | ||
| 
						 | 
					4fa959ba45 | ||
| 
						 | 
					b43712d78d | ||
| 
						 | 
					01904a0f10 | ||
| 
						 | 
					dd875e7529 | ||
| 
						 | 
					f1dcf0f0b8 | ||
| 
						 | 
					a045d001bf | ||
| 
						 | 
					066c1022d0 | ||
| 
						 | 
					59c192becc | ||
| 
						 | 
					a800816750 | ||
| 
						 | 
					970563e07b | 
							
								
								
									
										47
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										47
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,38 +18,23 @@ jobs:
 | 
			
		||||
    name: Build docker containers
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        build_type: ["hassio", "docker"]
 | 
			
		||||
        build_type: ["ha-addon", "docker", "lint"]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up env variables
 | 
			
		||||
        run: |
 | 
			
		||||
          base_version="3.4.0"
 | 
			
		||||
    - uses: actions/checkout@v2
 | 
			
		||||
    - name: Set up Python
 | 
			
		||||
      uses: actions/setup-python@v2
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: '3.9'
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        echo "TAG=check" >> $GITHUB_ENV
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
			
		||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
            build_to="esphome/esphome-hassio-${{ matrix.arch }}"
 | 
			
		||||
            dockerfile="docker/Dockerfile.hassio"
 | 
			
		||||
          else
 | 
			
		||||
            build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
            build_to="esphome/esphome-${{ matrix.arch }}"
 | 
			
		||||
            dockerfile="docker/Dockerfile"
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
          echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
 | 
			
		||||
          echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
 | 
			
		||||
          echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Pull for cache
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull "${BUILD_TO}:dev" || true
 | 
			
		||||
      - name: Register QEMU binfmt
 | 
			
		||||
        run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
 | 
			
		||||
      - run: |
 | 
			
		||||
          docker build \
 | 
			
		||||
            --build-arg "BUILD_FROM=${BUILD_FROM}" \
 | 
			
		||||
            --build-arg "BUILD_VERSION=ci" \
 | 
			
		||||
            --cache-from "${BUILD_TO}:dev" \
 | 
			
		||||
            --file "${DOCKERFILE}" \
 | 
			
		||||
            .
 | 
			
		||||
    - name: Run build
 | 
			
		||||
      run: |
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${TAG}" \
 | 
			
		||||
          --arch "${{ matrix.arch }}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          build
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										178
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										178
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,40 +4,36 @@ name: CI
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    # On dev branch release-dev already performs CI checks
 | 
			
		||||
    # On other branches the `pull_request` trigger will be used
 | 
			
		||||
    branches: [beta, release]
 | 
			
		||||
    branches: [dev, beta, release]
 | 
			
		||||
 | 
			
		||||
  pull_request:
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  lint-clang-format:
 | 
			
		||||
  ci-with-container:
 | 
			
		||||
    name: ${{ matrix.name }}
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: esphome/esphome-lint:1.1
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
        run: pio init --ide atom
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-format
 | 
			
		||||
        run: script/clang-format -i
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-clang-tidy:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: esphome/esphome-lint:1.1
 | 
			
		||||
    # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        split: [1, 2, 3, 4]
 | 
			
		||||
        include:
 | 
			
		||||
          - id: clang-format
 | 
			
		||||
            name: Run script/clang-format
 | 
			
		||||
          - id: clang-tidy
 | 
			
		||||
            name: Run script/clang-tidy 1/4
 | 
			
		||||
            split: 1
 | 
			
		||||
          - id: clang-tidy
 | 
			
		||||
            name: Run script/clang-tidy 2/4
 | 
			
		||||
            split: 2
 | 
			
		||||
          - id: clang-tidy
 | 
			
		||||
            name: Run script/clang-tidy 3/4
 | 
			
		||||
            split: 3
 | 
			
		||||
          - id: clang-tidy
 | 
			
		||||
            name: Run script/clang-tidy 4/4
 | 
			
		||||
            split: 4
 | 
			
		||||
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: ghcr.io/esphome/esphome-lint:1.1
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
@@ -45,26 +41,57 @@ jobs:
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
        run: pio init --ide atom
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-format
 | 
			
		||||
        run: script/clang-format -i
 | 
			
		||||
        if: ${{ matrix.id == 'clang-format' }}
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-tidy
 | 
			
		||||
        run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
 | 
			
		||||
        if: ${{ matrix.id == 'clang-tidy' }}
 | 
			
		||||
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-python:
 | 
			
		||||
  ci:
 | 
			
		||||
    # Don't use the esphome-lint docker image because it may contain outdated requirements.
 | 
			
		||||
    # This way, all dependencies are cached via the cache action.
 | 
			
		||||
    name: ${{ matrix.name }}
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        include:
 | 
			
		||||
          - id: ci-custom
 | 
			
		||||
            name: Run script/ci-custom
 | 
			
		||||
          - id: lint-python
 | 
			
		||||
            name: Run script/lint-python
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test1.yaml
 | 
			
		||||
            name: Test tests/test1.yaml
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test2.yaml
 | 
			
		||||
            name: Test tests/test2.yaml
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test3.yaml
 | 
			
		||||
            name: Test tests/test3.yaml
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test4.yaml
 | 
			
		||||
            name: Test tests/test4.yaml
 | 
			
		||||
          - id: pytest
 | 
			
		||||
            name: Run pytest
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
@@ -72,6 +99,17 @@ jobs:
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
 | 
			
		||||
      # Use per test platformio cache because tests have different platform versions
 | 
			
		||||
      - name: Cache ~/.platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            test-home-platformio-${{ matrix.file }}-
 | 
			
		||||
        if: ${{ matrix.id == 'test' }}
 | 
			
		||||
 | 
			
		||||
      - name: Set up python environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
 | 
			
		||||
@@ -80,82 +118,22 @@ jobs:
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/lint-python.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/pytest.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
 | 
			
		||||
      - name: Lint Custom
 | 
			
		||||
        run: script/ci-custom.py
 | 
			
		||||
        run: |
 | 
			
		||||
          script/ci-custom.py
 | 
			
		||||
          script/build_codeowners.py --check
 | 
			
		||||
        if: ${{ matrix.id == 'ci-custom' }}
 | 
			
		||||
      - name: Lint Python
 | 
			
		||||
        run: script/lint-python
 | 
			
		||||
      - name: Lint CODEOWNERS
 | 
			
		||||
        run: script/build_codeowners.py --check
 | 
			
		||||
        if: ${{ matrix.id == 'lint-python' }}
 | 
			
		||||
 | 
			
		||||
  test:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
          test:
 | 
			
		||||
          - test1
 | 
			
		||||
          - test2
 | 
			
		||||
          - test3
 | 
			
		||||
          - test4
 | 
			
		||||
          - test5
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      # Use per test platformio cache because tests have different platform versions
 | 
			
		||||
      - name: Cache ~/.platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            test-home-platformio-${{ matrix.test }}-
 | 
			
		||||
      - name: Set up environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
      - run: esphome compile ${{ matrix.file }}
 | 
			
		||||
        if: ${{ matrix.id == 'test' }}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - run: esphome compile tests/${{ matrix.test }}.yaml
 | 
			
		||||
 | 
			
		||||
  pytest:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      - name: Set up environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
      - name: Install Github Actions annotator
 | 
			
		||||
        run: pip install pytest-github-actions-annotate-failures
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - name: Run pytest
 | 
			
		||||
        run: |
 | 
			
		||||
          pytest \
 | 
			
		||||
            -qq \
 | 
			
		||||
            --durations=10 \
 | 
			
		||||
            -o console_output_style=count \
 | 
			
		||||
            tests
 | 
			
		||||
          pytest -vv --tb=native tests
 | 
			
		||||
        if: ${{ matrix.id == 'pytest' }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										108
									
								
								.github/workflows/docker-lint-build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										108
									
								
								.github/workflows/docker-lint-build.yml
									
									
									
									
										vendored
									
									
								
							@@ -13,30 +13,88 @@ on:
 | 
			
		||||
      - '.github/workflows/docker-lint-build.yml'
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  publish-docker-lint-iage:
 | 
			
		||||
    name: Build docker containers
 | 
			
		||||
  deploy-docker:
 | 
			
		||||
    name: Build and publish docker containers
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        build_type: ["lint"]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set TAG
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "TAG=1.1" >> $GITHUB_ENV
 | 
			
		||||
      - name: Pull for cache
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull "esphome/esphome-lint:latest" || true
 | 
			
		||||
      - name: Build
 | 
			
		||||
        run: |
 | 
			
		||||
          docker build \
 | 
			
		||||
            --cache-from "esphome/esphome-lint:latest" \
 | 
			
		||||
            --file "docker/Dockerfile.lint" \
 | 
			
		||||
            --tag "esphome/esphome-lint:latest" \
 | 
			
		||||
            --tag "esphome/esphome-lint:${TAG}" \
 | 
			
		||||
            .
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
        env:
 | 
			
		||||
          DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
        run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
			
		||||
      - run: |
 | 
			
		||||
          docker push "esphome/esphome-lint:${TAG}"
 | 
			
		||||
          docker push "esphome/esphome-lint:latest"
 | 
			
		||||
    - uses: actions/checkout@v2
 | 
			
		||||
    - name: Set up Python
 | 
			
		||||
      uses: actions/setup-python@v2
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: '3.9'
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        echo "TAG=1.1" >> $GITHUB_ENV
 | 
			
		||||
 | 
			
		||||
    - name: Run build
 | 
			
		||||
      run: |
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${TAG}" \
 | 
			
		||||
          --arch "${{ matrix.arch }}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          build
 | 
			
		||||
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
        username: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
        password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
    - name: Log in to the GitHub container registry
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
    - name: Run push
 | 
			
		||||
      run: |
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${TAG}" \
 | 
			
		||||
          --arch "${{ matrix.arch }}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          push
 | 
			
		||||
 | 
			
		||||
  deploy-docker-manifest:
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [deploy-docker]
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        build_type: ["lint"]
 | 
			
		||||
    steps:
 | 
			
		||||
    - uses: actions/checkout@v2
 | 
			
		||||
    - name: Set up Python
 | 
			
		||||
      uses: actions/setup-python@v2
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: '3.9'
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        echo "TAG=1.1" >> $GITHUB_ENV
 | 
			
		||||
    - name: Enable experimental manifest support
 | 
			
		||||
      run: |
 | 
			
		||||
        mkdir -p ~/.docker
 | 
			
		||||
        echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
 | 
			
		||||
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
        username: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
        password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
    - name: Log in to the GitHub container registry
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
    - name: Run manifest
 | 
			
		||||
      run: |
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${TAG}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          manifest
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								.github/workflows/matchers/pytest.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/matchers/pytest.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
{
 | 
			
		||||
  "problemMatcher": [
 | 
			
		||||
    {
 | 
			
		||||
      "owner": "pytest",
 | 
			
		||||
      "fileLocation": "absolute",
 | 
			
		||||
      "pattern": [
 | 
			
		||||
        {
 | 
			
		||||
          "regexp": "^\\s+File \"(.*)\", line (\\d+), in (.*)$",
 | 
			
		||||
          "file": 1,
 | 
			
		||||
          "line": 2
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "regexp": "^\\s+(.*)$",
 | 
			
		||||
          "message": 1
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										247
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										247
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,247 +0,0 @@
 | 
			
		||||
name: Publish dev releases to docker hub
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
    - dev
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
 | 
			
		||||
 | 
			
		||||
  lint-clang-format:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: esphome/esphome-lint:1.1
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
        run: pio init --ide atom
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-format
 | 
			
		||||
        run: script/clang-format -i
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-clang-tidy:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: esphome/esphome-lint:1.1
 | 
			
		||||
    # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        split: [1, 2, 3, 4]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
        run: pio init --ide atom
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
      - name: Run clang-tidy
 | 
			
		||||
        run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-python:
 | 
			
		||||
    # Don't use the esphome-lint docker image because it may contain outdated requirements.
 | 
			
		||||
    # This way, all dependencies are cached via the cache action.
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      - name: Set up python environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/lint-python.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - name: Lint Custom
 | 
			
		||||
        run: script/ci-custom.py
 | 
			
		||||
      - name: Lint Python
 | 
			
		||||
        run: script/lint-python
 | 
			
		||||
      - name: Lint CODEOWNERS
 | 
			
		||||
        run: script/build_codeowners.py --check
 | 
			
		||||
 | 
			
		||||
  test:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
          test:
 | 
			
		||||
          - test1
 | 
			
		||||
          - test2
 | 
			
		||||
          - test3
 | 
			
		||||
          - test4
 | 
			
		||||
          - test5
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      # Use per test platformio cache because tests have different platform versions
 | 
			
		||||
      - name: Cache ~/.platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            test-home-platformio-${{ matrix.test }}-
 | 
			
		||||
      - name: Set up environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - run: esphome compile tests/${{ matrix.test }}.yaml
 | 
			
		||||
 | 
			
		||||
  pytest:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      - name: Set up environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
      - name: Install Github Actions annotator
 | 
			
		||||
        run: pip install pytest-github-actions-annotate-failures
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - name: Run pytest
 | 
			
		||||
        run: |
 | 
			
		||||
          pytest \
 | 
			
		||||
            -qq \
 | 
			
		||||
            --durations=10 \
 | 
			
		||||
            -o console_output_style=count \
 | 
			
		||||
            tests
 | 
			
		||||
 | 
			
		||||
  deploy-docker:
 | 
			
		||||
    name: Build and publish docker containers
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        # Hassio dev image doesn't use esphome/esphome-hassio-$arch and uses base directly
 | 
			
		||||
        build_type: ["docker"]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set TAG
 | 
			
		||||
        run: |
 | 
			
		||||
          TAG="${GITHUB_SHA:0:7}"
 | 
			
		||||
          echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Set up env variables
 | 
			
		||||
        run: |
 | 
			
		||||
          base_version="3.4.0"
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
			
		||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
            build_to="esphome/esphome-hassio-${{ matrix.arch }}"
 | 
			
		||||
            dockerfile="docker/Dockerfile.hassio"
 | 
			
		||||
          else
 | 
			
		||||
            build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
            build_to="esphome/esphome-${{ matrix.arch }}"
 | 
			
		||||
            dockerfile="docker/Dockerfile"
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
          echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
 | 
			
		||||
          echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
 | 
			
		||||
          echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Pull for cache
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull "${BUILD_TO}:dev" || true
 | 
			
		||||
      - name: Register QEMU binfmt
 | 
			
		||||
        run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
 | 
			
		||||
      - run: |
 | 
			
		||||
          docker build \
 | 
			
		||||
            --build-arg "BUILD_FROM=${BUILD_FROM}" \
 | 
			
		||||
            --build-arg "BUILD_VERSION=${TAG}" \
 | 
			
		||||
            --tag "${BUILD_TO}:${TAG}" \
 | 
			
		||||
            --tag "${BUILD_TO}:dev" \
 | 
			
		||||
            --cache-from "${BUILD_TO}:dev" \
 | 
			
		||||
            --file "${DOCKERFILE}" \
 | 
			
		||||
            .
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
        env:
 | 
			
		||||
          DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
        run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
			
		||||
      - run: |
 | 
			
		||||
          docker push "${BUILD_TO}:${TAG}"
 | 
			
		||||
          docker push "${BUILD_TO}:dev"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  deploy-docker-manifest:
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [deploy-docker]
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Enable experimental manifest support
 | 
			
		||||
      run: |
 | 
			
		||||
        mkdir -p ~/.docker
 | 
			
		||||
        echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        TAG="${GITHUB_SHA:0:7}"
 | 
			
		||||
        echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
        DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
      run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
			
		||||
    - name: "Create the manifest"
 | 
			
		||||
      run: |
 | 
			
		||||
        docker manifest create esphome/esphome:${TAG} \
 | 
			
		||||
          esphome/esphome-aarch64:${TAG} \
 | 
			
		||||
          esphome/esphome-amd64:${TAG} \
 | 
			
		||||
          esphome/esphome-armv7:${TAG}
 | 
			
		||||
        docker manifest push esphome/esphome:${TAG}
 | 
			
		||||
 | 
			
		||||
        docker manifest create esphome/esphome:dev \
 | 
			
		||||
          esphome/esphome-aarch64:${TAG} \
 | 
			
		||||
          esphome/esphome-amd64:${TAG} \
 | 
			
		||||
          esphome/esphome-armv7:${TAG}
 | 
			
		||||
        docker manifest push esphome/esphome:dev
 | 
			
		||||
							
								
								
									
										315
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										315
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,164 +1,35 @@
 | 
			
		||||
name: Publish Release
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
  release:
 | 
			
		||||
    types: [published]
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: "0 2 * * *"
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
 | 
			
		||||
 | 
			
		||||
  lint-clang-format:
 | 
			
		||||
  init:
 | 
			
		||||
    name: Initialize build
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: esphome/esphome-lint:1.1
 | 
			
		||||
    outputs:
 | 
			
		||||
      tag: ${{ steps.tag.outputs.tag }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
        run: pio init --ide atom
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-format
 | 
			
		||||
        run: script/clang-format -i
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-clang-tidy:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: esphome/esphome-lint:1.1
 | 
			
		||||
    # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        split: [1, 2, 3, 4]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
        run: pio init --ide atom
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
      - name: Get tag
 | 
			
		||||
        id: tag
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
      - name: Run clang-tidy
 | 
			
		||||
        run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-python:
 | 
			
		||||
    # Don't use the esphome-lint docker image because it may contain outdated requirements.
 | 
			
		||||
    # This way, all dependencies are cached via the cache action.
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      - name: Set up python environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/lint-python.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - name: Lint Custom
 | 
			
		||||
        run: script/ci-custom.py
 | 
			
		||||
      - name: Lint Python
 | 
			
		||||
        run: script/lint-python
 | 
			
		||||
      - name: Lint CODEOWNERS
 | 
			
		||||
        run: script/build_codeowners.py --check
 | 
			
		||||
 | 
			
		||||
  test:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
          test:
 | 
			
		||||
          - test1
 | 
			
		||||
          - test2
 | 
			
		||||
          - test3
 | 
			
		||||
          - test4
 | 
			
		||||
          - test5
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      # Use per test platformio cache because tests have different platform versions
 | 
			
		||||
      - name: Cache ~/.platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            test-home-platformio-${{ matrix.test }}-
 | 
			
		||||
      - name: Set up environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - run: esphome compile tests/${{ matrix.test }}.yaml
 | 
			
		||||
 | 
			
		||||
  pytest:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      - name: Set up environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
      - name: Install Github Actions annotator
 | 
			
		||||
        run: pip install pytest-github-actions-annotate-failures
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - name: Run pytest
 | 
			
		||||
        run: |
 | 
			
		||||
          pytest \
 | 
			
		||||
            -qq \
 | 
			
		||||
            --durations=10 \
 | 
			
		||||
            -o console_output_style=count \
 | 
			
		||||
            tests
 | 
			
		||||
          if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then
 | 
			
		||||
            TAG="${GITHUB_REF#refs/tags/v}"
 | 
			
		||||
          else
 | 
			
		||||
            TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
 | 
			
		||||
            today="$(date --utc '+%Y%m%d')"
 | 
			
		||||
            TAG="${TAG}${today}"
 | 
			
		||||
          fi
 | 
			
		||||
          echo "::set-output name=tag::${TAG}"
 | 
			
		||||
 | 
			
		||||
  deploy-pypi:
 | 
			
		||||
    name: Build and publish to PyPi
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
 | 
			
		||||
    if: github.repository == 'esphome/esphome' && github.event_name == 'release'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
@@ -182,119 +53,85 @@ jobs:
 | 
			
		||||
    name: Build and publish docker containers
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
 | 
			
		||||
    needs: [init]
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        build_type: ["hassio", "docker"]
 | 
			
		||||
        build_type: ["ha-addon", "docker"]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set TAG
 | 
			
		||||
        run: |
 | 
			
		||||
          TAG="${GITHUB_REF#refs/tags/v}"
 | 
			
		||||
          echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Set up env variables
 | 
			
		||||
        run: |
 | 
			
		||||
          base_version="3.4.0"
 | 
			
		||||
    - uses: actions/checkout@v2
 | 
			
		||||
    - name: Set up Python
 | 
			
		||||
      uses: actions/setup-python@v2
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: '3.9'
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
			
		||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
            build_to="esphome/esphome-hassio-${{ matrix.arch }}"
 | 
			
		||||
            dockerfile="docker/Dockerfile.hassio"
 | 
			
		||||
          else
 | 
			
		||||
            build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
            build_to="esphome/esphome-${{ matrix.arch }}"
 | 
			
		||||
            dockerfile="docker/Dockerfile"
 | 
			
		||||
          fi
 | 
			
		||||
    - name: Run build
 | 
			
		||||
      run: |
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${{ needs.init.outputs.tag }}" \
 | 
			
		||||
          --arch "${{ matrix.arch }}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          build
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
 | 
			
		||||
            cache_tag="beta"
 | 
			
		||||
          else
 | 
			
		||||
            cache_tag="latest"
 | 
			
		||||
          fi
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
        username: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
        password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
    - name: Log in to the GitHub container registry
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
          # Set env variables so these values don't need to be calculated again
 | 
			
		||||
          echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
 | 
			
		||||
          echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
 | 
			
		||||
          echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
 | 
			
		||||
          echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Pull for cache
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull "${BUILD_TO}:${CACHE_TAG}" || true
 | 
			
		||||
      - name: Register QEMU binfmt
 | 
			
		||||
        run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
 | 
			
		||||
      - run: |
 | 
			
		||||
          docker build \
 | 
			
		||||
            --build-arg "BUILD_FROM=${BUILD_FROM}" \
 | 
			
		||||
            --build-arg "BUILD_VERSION=${TAG}" \
 | 
			
		||||
            --tag "${BUILD_TO}:${TAG}" \
 | 
			
		||||
            --cache-from "${BUILD_TO}:${CACHE_TAG}" \
 | 
			
		||||
            --file "${DOCKERFILE}" \
 | 
			
		||||
            .
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
        env:
 | 
			
		||||
          DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
        run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
			
		||||
      - run: docker push "${BUILD_TO}:${TAG}"
 | 
			
		||||
 | 
			
		||||
      # Always publish to beta tag (also full releases)
 | 
			
		||||
      - name: Publish docker beta tag
 | 
			
		||||
        run: |
 | 
			
		||||
          docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta"
 | 
			
		||||
          docker push "${BUILD_TO}:beta"
 | 
			
		||||
 | 
			
		||||
      - if: ${{ !github.event.release.prerelease }}
 | 
			
		||||
        name: Publish docker latest tag
 | 
			
		||||
        run: |
 | 
			
		||||
          docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest"
 | 
			
		||||
          docker push "${BUILD_TO}:latest"
 | 
			
		||||
    - name: Run push
 | 
			
		||||
      run: |
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${{ needs.init.outputs.tag }}" \
 | 
			
		||||
          --arch "${{ matrix.arch }}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          push
 | 
			
		||||
 | 
			
		||||
  deploy-docker-manifest:
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [deploy-docker]
 | 
			
		||||
    needs: [init, deploy-docker]
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        build_type: ["ha-addon", "docker"]
 | 
			
		||||
    steps:
 | 
			
		||||
    - uses: actions/checkout@v2
 | 
			
		||||
    - name: Set up Python
 | 
			
		||||
      uses: actions/setup-python@v2
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: '3.9'
 | 
			
		||||
    - name: Enable experimental manifest support
 | 
			
		||||
      run: |
 | 
			
		||||
        mkdir -p ~/.docker
 | 
			
		||||
        echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        TAG="${GITHUB_REF#refs/tags/v}"
 | 
			
		||||
        echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
        DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
      run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
			
		||||
    - name: "Create the manifest"
 | 
			
		||||
      run: |
 | 
			
		||||
        docker manifest create esphome/esphome:${TAG} \
 | 
			
		||||
          esphome/esphome-aarch64:${TAG} \
 | 
			
		||||
          esphome/esphome-amd64:${TAG} \
 | 
			
		||||
          esphome/esphome-armv7:${TAG}
 | 
			
		||||
        docker manifest push esphome/esphome:${TAG}
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
        username: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
        password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
    - name: Log in to the GitHub container registry
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
    - name: Publish docker beta tag
 | 
			
		||||
    - name: Run manifest
 | 
			
		||||
      run: |
 | 
			
		||||
        docker manifest create esphome/esphome:beta \
 | 
			
		||||
          esphome/esphome-aarch64:${TAG} \
 | 
			
		||||
          esphome/esphome-amd64:${TAG} \
 | 
			
		||||
          esphome/esphome-armv7:${TAG}
 | 
			
		||||
        docker manifest push esphome/esphome:beta
 | 
			
		||||
 | 
			
		||||
    - name: Publish docker latest tag
 | 
			
		||||
      if: ${{ !github.event.release.prerelease }}
 | 
			
		||||
      run: |
 | 
			
		||||
        docker manifest create esphome/esphome:latest \
 | 
			
		||||
          esphome/esphome-aarch64:${TAG} \
 | 
			
		||||
          esphome/esphome-amd64:${TAG} \
 | 
			
		||||
          esphome/esphome-armv7:${TAG}
 | 
			
		||||
        docker manifest push esphome/esphome:latest
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${{ needs.init.outputs.tag }}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          manifest
 | 
			
		||||
 | 
			
		||||
  deploy-hassio-repo:
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    if: github.repository == 'esphome/esphome' && github.event_name == 'release'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [deploy-docker]
 | 
			
		||||
    steps:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -13,6 +13,9 @@ __pycache__/
 | 
			
		||||
# Intellij Idea
 | 
			
		||||
.idea
 | 
			
		||||
 | 
			
		||||
# Vim
 | 
			
		||||
*.swp
 | 
			
		||||
 | 
			
		||||
# Hide some OS X stuff
 | 
			
		||||
.DS_Store
 | 
			
		||||
.AppleDouble
 | 
			
		||||
 
 | 
			
		||||
@@ -73,6 +73,11 @@ esphome/components/midea_ac/* @dudanov
 | 
			
		||||
esphome/components/midea_dongle/* @dudanov
 | 
			
		||||
esphome/components/mitsubishi/* @RubyBailey
 | 
			
		||||
esphome/components/network/* @esphome/core
 | 
			
		||||
esphome/components/nextion/* @senexcrenshaw
 | 
			
		||||
esphome/components/nextion/binary_sensor/* @senexcrenshaw
 | 
			
		||||
esphome/components/nextion/sensor/* @senexcrenshaw
 | 
			
		||||
esphome/components/nextion/switch/* @senexcrenshaw
 | 
			
		||||
esphome/components/nextion/text_sensor/* @senexcrenshaw
 | 
			
		||||
esphome/components/nfc/* @jesserockz
 | 
			
		||||
esphome/components/number/* @esphome/core
 | 
			
		||||
esphome/components/ota/* @esphome/core
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
ARG BUILD_FROM=esphome/esphome-base-amd64:3.4.0
 | 
			
		||||
ARG BUILD_FROM=esphome/esphome-base:latest
 | 
			
		||||
FROM ${BUILD_FROM}
 | 
			
		||||
 | 
			
		||||
# First install requirements to leverage caching when requirements don't change
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
ARG BUILD_FROM
 | 
			
		||||
ARG BUILD_FROM=esphome/esphome-hassio-base:latest
 | 
			
		||||
FROM ${BUILD_FROM}
 | 
			
		||||
 | 
			
		||||
# First install requirements to leverage caching when requirements don't change
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
FROM esphome/esphome-lint-base:3.4.0
 | 
			
		||||
ARG BUILD_FROM=esphome/esphome-lint-base:latest
 | 
			
		||||
FROM ${BUILD_FROM}
 | 
			
		||||
 | 
			
		||||
COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py  platformio.ini /
 | 
			
		||||
RUN \
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										177
									
								
								docker/build.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										177
									
								
								docker/build.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,177 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
import subprocess
 | 
			
		||||
import argparse
 | 
			
		||||
import platform
 | 
			
		||||
import shlex
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CHANNEL_DEV = 'dev'
 | 
			
		||||
CHANNEL_BETA = 'beta'
 | 
			
		||||
CHANNEL_RELEASE = 'release'
 | 
			
		||||
CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
 | 
			
		||||
 | 
			
		||||
ARCH_AMD64 = 'amd64'
 | 
			
		||||
ARCH_ARMV7 = 'armv7'
 | 
			
		||||
ARCH_AARCH64 = 'aarch64'
 | 
			
		||||
ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64]
 | 
			
		||||
 | 
			
		||||
TYPE_DOCKER = 'docker'
 | 
			
		||||
TYPE_HA_ADDON = 'ha-addon'
 | 
			
		||||
TYPE_LINT = 'lint'
 | 
			
		||||
TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BASE_VERSION = "3.6.0"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
parser = argparse.ArgumentParser()
 | 
			
		||||
parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag")
 | 
			
		||||
parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for")
 | 
			
		||||
parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run")
 | 
			
		||||
parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them")
 | 
			
		||||
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
 | 
			
		||||
build_parser = subparsers.add_parser("build", help="Build the image")
 | 
			
		||||
push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub")
 | 
			
		||||
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# only lists some possibilities, doesn't have to be perfect
 | 
			
		||||
# https://stackoverflow.com/a/45125525
 | 
			
		||||
UNAME_TO_ARCH = {
 | 
			
		||||
    "x86_64": ARCH_AMD64,
 | 
			
		||||
    "aarch64": ARCH_AARCH64,
 | 
			
		||||
    "aarch64_be": ARCH_AARCH64,
 | 
			
		||||
    "arm": ARCH_ARMV7,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dataclass(frozen=True)
 | 
			
		||||
class DockerParams:
 | 
			
		||||
    build_from: str
 | 
			
		||||
    build_to: str
 | 
			
		||||
    manifest_to: str
 | 
			
		||||
    dockerfile: str
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def for_type_arch(cls, build_type, arch):
 | 
			
		||||
        prefix = {
 | 
			
		||||
            TYPE_DOCKER: "esphome/esphome",
 | 
			
		||||
            TYPE_HA_ADDON: "esphome/esphome-hassio",
 | 
			
		||||
            TYPE_LINT: "esphome/esphome-lint"
 | 
			
		||||
        }[build_type]
 | 
			
		||||
        build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}"
 | 
			
		||||
        build_to = f"{prefix}-{arch}"
 | 
			
		||||
        dockerfile = {
 | 
			
		||||
            TYPE_DOCKER: "docker/Dockerfile",
 | 
			
		||||
            TYPE_HA_ADDON: "docker/Dockerfile.hassio",
 | 
			
		||||
            TYPE_LINT: "docker/Dockerfile.lint",
 | 
			
		||||
        }[build_type]
 | 
			
		||||
        return cls(
 | 
			
		||||
            build_from=build_from,
 | 
			
		||||
            build_to=build_to,
 | 
			
		||||
            manifest_to=prefix,
 | 
			
		||||
            dockerfile=dockerfile
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    def run_command(*cmd, ignore_error: bool = False):
 | 
			
		||||
        print(f"$ {shlex.join(list(cmd))}")
 | 
			
		||||
        if not args.dry_run:
 | 
			
		||||
            rc = subprocess.call(list(cmd))
 | 
			
		||||
            if rc != 0 and not ignore_error:
 | 
			
		||||
                print("Command failed")
 | 
			
		||||
                sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    # detect channel from tag
 | 
			
		||||
    match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag)
 | 
			
		||||
    if match is None:
 | 
			
		||||
        channel = CHANNEL_DEV
 | 
			
		||||
    elif match.group(1) is None:
 | 
			
		||||
        channel = CHANNEL_RELEASE
 | 
			
		||||
    else:
 | 
			
		||||
        channel = CHANNEL_BETA
 | 
			
		||||
 | 
			
		||||
    tags_to_push = [args.tag]
 | 
			
		||||
    if channel == CHANNEL_DEV:
 | 
			
		||||
        tags_to_push.append("dev")
 | 
			
		||||
    elif channel == CHANNEL_BETA:
 | 
			
		||||
        tags_to_push.append("beta")
 | 
			
		||||
    elif channel == CHANNEL_RELEASE:
 | 
			
		||||
        # Additionally push to beta
 | 
			
		||||
        tags_to_push.append("beta")
 | 
			
		||||
        tags_to_push.append("latest")
 | 
			
		||||
 | 
			
		||||
    if args.command == "build":
 | 
			
		||||
        # 1. pull cache image
 | 
			
		||||
        params = DockerParams.for_type_arch(args.build_type, args.arch)
 | 
			
		||||
        cache_tag = {
 | 
			
		||||
            CHANNEL_DEV: "dev",
 | 
			
		||||
            CHANNEL_BETA: "beta",
 | 
			
		||||
            CHANNEL_RELEASE: "latest",
 | 
			
		||||
        }[channel]
 | 
			
		||||
        cache_img = f"ghcr.io/{params.build_to}:{cache_tag}"
 | 
			
		||||
        run_command("docker", "pull", cache_img, ignore_error=True)
 | 
			
		||||
 | 
			
		||||
        # 2. register QEMU binfmt (if not host arch)
 | 
			
		||||
        is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch
 | 
			
		||||
        if not is_native:
 | 
			
		||||
            run_command(
 | 
			
		||||
                "docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2",
 | 
			
		||||
                "--reset", "-p", "yes"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        # 3. build
 | 
			
		||||
        run_command(
 | 
			
		||||
            "docker", "build",
 | 
			
		||||
            "--build-arg", f"BUILD_FROM={params.build_from}",
 | 
			
		||||
            "--build-arg", f"BUILD_VERSION={args.tag}",
 | 
			
		||||
            "--tag", f"{params.build_to}:{args.tag}",
 | 
			
		||||
            "--cache-from", cache_img,
 | 
			
		||||
            "--file", params.dockerfile,
 | 
			
		||||
            "."
 | 
			
		||||
        )
 | 
			
		||||
    elif args.command == "push":
 | 
			
		||||
        params = DockerParams.for_type_arch(args.build_type, args.arch)
 | 
			
		||||
        imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push]
 | 
			
		||||
        imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push]
 | 
			
		||||
        src = imgs[0]
 | 
			
		||||
        # 1. tag images
 | 
			
		||||
        for img in imgs[1:]:
 | 
			
		||||
            run_command(
 | 
			
		||||
                "docker", "tag", src, img
 | 
			
		||||
            )
 | 
			
		||||
        # 2. push images
 | 
			
		||||
        for img in imgs:
 | 
			
		||||
            run_command(
 | 
			
		||||
                "docker", "push", img
 | 
			
		||||
            )
 | 
			
		||||
    elif args.command == "manifest":
 | 
			
		||||
        manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to
 | 
			
		||||
 | 
			
		||||
        targets = [f"{manifest}:{tag}" for tag in tags_to_push]
 | 
			
		||||
        targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push]
 | 
			
		||||
        # 1. Create manifests
 | 
			
		||||
        for target in targets:
 | 
			
		||||
            cmd = ["docker", "manifest", "create", target]
 | 
			
		||||
            for arch in ARCHS:
 | 
			
		||||
                src = f"{DockerParams.for_type_arch(args.build_type, arch).build_to}:{args.tag}"
 | 
			
		||||
                if target.startswith("ghcr.io"):
 | 
			
		||||
                    src = f"ghcr.io/{src}"
 | 
			
		||||
                cmd.append(src)
 | 
			
		||||
            run_command(*cmd)
 | 
			
		||||
        # 2. Push manifests
 | 
			
		||||
        for target in targets:
 | 
			
		||||
            run_command(
 | 
			
		||||
                "docker", "manifest", "push", target
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
@@ -11,7 +11,30 @@ namespace adc {
 | 
			
		||||
static const char *const TAG = "adc";
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; }
 | 
			
		||||
void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
 | 
			
		||||
 | 
			
		||||
inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
 | 
			
		||||
  switch (pin) {
 | 
			
		||||
    case 36:
 | 
			
		||||
      return ADC1_CHANNEL_0;
 | 
			
		||||
    case 37:
 | 
			
		||||
      return ADC1_CHANNEL_1;
 | 
			
		||||
    case 38:
 | 
			
		||||
      return ADC1_CHANNEL_2;
 | 
			
		||||
    case 39:
 | 
			
		||||
      return ADC1_CHANNEL_3;
 | 
			
		||||
    case 32:
 | 
			
		||||
      return ADC1_CHANNEL_4;
 | 
			
		||||
    case 33:
 | 
			
		||||
      return ADC1_CHANNEL_5;
 | 
			
		||||
    case 34:
 | 
			
		||||
      return ADC1_CHANNEL_6;
 | 
			
		||||
    case 35:
 | 
			
		||||
      return ADC1_CHANNEL_7;
 | 
			
		||||
    default:
 | 
			
		||||
      return ADC1_CHANNEL_MAX;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void ADCSensor::setup() {
 | 
			
		||||
@@ -21,7 +44,9 @@ void ADCSensor::setup() {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
  analogSetPinAttenuation(this->pin_, this->attenuation_);
 | 
			
		||||
  adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_);
 | 
			
		||||
  adc1_config_width(ADC_WIDTH_BIT_12);
 | 
			
		||||
  adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_));
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
void ADCSensor::dump_config() {
 | 
			
		||||
@@ -36,18 +61,20 @@ void ADCSensor::dump_config() {
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Pin: %u", this->pin_);
 | 
			
		||||
  switch (this->attenuation_) {
 | 
			
		||||
    case ADC_0db:
 | 
			
		||||
    case ADC_ATTEN_DB_0:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_2_5db:
 | 
			
		||||
    case ADC_ATTEN_DB_2_5:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_6db:
 | 
			
		||||
    case ADC_ATTEN_DB_6:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_11db:
 | 
			
		||||
    case ADC_ATTEN_DB_11:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
 | 
			
		||||
      break;
 | 
			
		||||
    default:  // This is to satisfy the unused ADC_ATTEN_MAX
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
@@ -60,20 +87,23 @@ void ADCSensor::update() {
 | 
			
		||||
}
 | 
			
		||||
float ADCSensor::sample() {
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
  float value_v = analogRead(this->pin_) / 4095.0f;  // NOLINT
 | 
			
		||||
  int raw = adc1_get_raw(gpio_to_adc1(pin_));
 | 
			
		||||
  float value_v = raw / 4095.0f;
 | 
			
		||||
  switch (this->attenuation_) {
 | 
			
		||||
    case ADC_0db:
 | 
			
		||||
    case ADC_ATTEN_DB_0:
 | 
			
		||||
      value_v *= 1.1;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_2_5db:
 | 
			
		||||
    case ADC_ATTEN_DB_2_5:
 | 
			
		||||
      value_v *= 1.5;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_6db:
 | 
			
		||||
    case ADC_ATTEN_DB_6:
 | 
			
		||||
      value_v *= 2.2;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_11db:
 | 
			
		||||
    case ADC_ATTEN_DB_11:
 | 
			
		||||
      value_v *= 3.9;
 | 
			
		||||
      break;
 | 
			
		||||
    default:  // This is to satisfy the unused ADC_ATTEN_MAX
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  return value_v;
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,10 @@
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
#include "driver/adc.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace adc {
 | 
			
		||||
 | 
			
		||||
@@ -13,7 +17,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
 public:
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
  /// Set the attenuation for this pin. Only available on the ESP32.
 | 
			
		||||
  void set_attenuation(adc_attenuation_t attenuation);
 | 
			
		||||
  void set_attenuation(adc_atten_t attenuation);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  /// Update adc values.
 | 
			
		||||
@@ -34,7 +38,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
  uint8_t pin_;
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
  adc_attenuation_t attenuation_{ADC_0db};
 | 
			
		||||
  adc_atten_t attenuation_{ADC_ATTEN_DB_0};
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,10 +16,10 @@ from esphome.const import (
 | 
			
		||||
AUTO_LOAD = ["voltage_sampler"]
 | 
			
		||||
 | 
			
		||||
ATTENUATION_MODES = {
 | 
			
		||||
    "0db": cg.global_ns.ADC_0db,
 | 
			
		||||
    "2.5db": cg.global_ns.ADC_2_5db,
 | 
			
		||||
    "6db": cg.global_ns.ADC_6db,
 | 
			
		||||
    "11db": cg.global_ns.ADC_11db,
 | 
			
		||||
    "0db": cg.global_ns.ADC_ATTEN_DB_0,
 | 
			
		||||
    "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
 | 
			
		||||
    "6db": cg.global_ns.ADC_ATTEN_DB_6,
 | 
			
		||||
    "11db": cg.global_ns.ADC_ATTEN_DB_11,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -64,11 +64,6 @@ void ADS1115Component::setup() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->prev_config_ = config;
 | 
			
		||||
 | 
			
		||||
  for (auto *sensor : this->sensors_) {
 | 
			
		||||
    this->set_interval(sensor->get_name(), sensor->update_interval(),
 | 
			
		||||
                       [this, sensor] { this->request_measurement(sensor); });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void ADS1115Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
 | 
			
		||||
@@ -107,17 +102,22 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
 | 
			
		||||
    }
 | 
			
		||||
    this->prev_config_ = config;
 | 
			
		||||
 | 
			
		||||
    // about 1.6 ms with 860 samples per second
 | 
			
		||||
    // about 1.2 ms with 860 samples per second
 | 
			
		||||
    delay(2);
 | 
			
		||||
 | 
			
		||||
    uint32_t start = millis();
 | 
			
		||||
    while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
 | 
			
		||||
      if (millis() - start > 100) {
 | 
			
		||||
        ESP_LOGW(TAG, "Reading ADS1115 timed out");
 | 
			
		||||
        this->status_set_warning();
 | 
			
		||||
        return NAN;
 | 
			
		||||
    // in continuous mode, conversion will always be running, rely on the delay
 | 
			
		||||
    // to ensure conversion is taking place with the correct settings
 | 
			
		||||
    // can we use the rdy pin to trigger when a conversion is done?
 | 
			
		||||
    if (!this->continuous_mode_) {
 | 
			
		||||
      uint32_t start = millis();
 | 
			
		||||
      while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
 | 
			
		||||
        if (millis() - start > 100) {
 | 
			
		||||
          ESP_LOGW(TAG, "Reading ADS1115 timed out");
 | 
			
		||||
          this->status_set_warning();
 | 
			
		||||
          return NAN;
 | 
			
		||||
        }
 | 
			
		||||
        yield();
 | 
			
		||||
      }
 | 
			
		||||
      yield();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
 | 
			
		||||
      auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
 | 
			
		||||
      if (chr == nullptr) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str());
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str());
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      this->char_handle_ = chr->handle;
 | 
			
		||||
 
 | 
			
		||||
@@ -422,6 +422,12 @@ enum SensorStateClass {
 | 
			
		||||
  STATE_CLASS_MEASUREMENT = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum SensorLastResetType {
 | 
			
		||||
  LAST_RESET_NONE = 0;
 | 
			
		||||
  LAST_RESET_NEVER = 1;
 | 
			
		||||
  LAST_RESET_AUTO = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message ListEntitiesSensorResponse {
 | 
			
		||||
  option (id) = 16;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
@@ -438,6 +444,7 @@ message ListEntitiesSensorResponse {
 | 
			
		||||
  bool force_update = 8;
 | 
			
		||||
  string device_class = 9;
 | 
			
		||||
  SensorStateClass state_class = 10;
 | 
			
		||||
  SensorLastResetType last_reset_type = 11;
 | 
			
		||||
}
 | 
			
		||||
message SensorStateResponse {
 | 
			
		||||
  option (id) = 25;
 | 
			
		||||
 
 | 
			
		||||
@@ -399,6 +399,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
 | 
			
		||||
  msg.force_update = sensor->get_force_update();
 | 
			
		||||
  msg.device_class = sensor->get_device_class();
 | 
			
		||||
  msg.state_class = static_cast<enums::SensorStateClass>(sensor->state_class);
 | 
			
		||||
  msg.last_reset_type = static_cast<enums::SensorLastResetType>(sensor->last_reset_type);
 | 
			
		||||
 | 
			
		||||
  return this->send_list_entities_sensor_response(msg);
 | 
			
		||||
}
 | 
			
		||||
@@ -570,11 +571,11 @@ bool APIConnection::send_number_info(number::Number *number) {
 | 
			
		||||
  msg.object_id = number->get_object_id();
 | 
			
		||||
  msg.name = number->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("number", number);
 | 
			
		||||
  msg.icon = number->get_icon();
 | 
			
		||||
  msg.icon = number->traits.get_icon();
 | 
			
		||||
 | 
			
		||||
  msg.min_value = number->get_min_value();
 | 
			
		||||
  msg.max_value = number->get_max_value();
 | 
			
		||||
  msg.step = number->get_step();
 | 
			
		||||
  msg.min_value = number->traits.get_min_value();
 | 
			
		||||
  msg.max_value = number->traits.get_max_value();
 | 
			
		||||
  msg.step = number->traits.get_step();
 | 
			
		||||
 | 
			
		||||
  return this->send_list_entities_number_response(msg);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -72,6 +72,18 @@ template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::Sens
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::SensorLastResetType>(enums::SensorLastResetType value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::LAST_RESET_NONE:
 | 
			
		||||
      return "LAST_RESET_NONE";
 | 
			
		||||
    case enums::LAST_RESET_NEVER:
 | 
			
		||||
      return "LAST_RESET_NEVER";
 | 
			
		||||
    case enums::LAST_RESET_AUTO:
 | 
			
		||||
      return "LAST_RESET_AUTO";
 | 
			
		||||
    default:
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::LOG_LEVEL_NONE:
 | 
			
		||||
@@ -1592,6 +1604,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
 | 
			
		||||
      this->state_class = value.as_enum<enums::SensorStateClass>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 11: {
 | 
			
		||||
      this->last_reset_type = value.as_enum<enums::SensorLastResetType>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -1647,6 +1663,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_bool(8, this->force_update);
 | 
			
		||||
  buffer.encode_string(9, this->device_class);
 | 
			
		||||
  buffer.encode_enum<enums::SensorStateClass>(10, this->state_class);
 | 
			
		||||
  buffer.encode_enum<enums::SensorLastResetType>(11, this->last_reset_type);
 | 
			
		||||
}
 | 
			
		||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
@@ -1692,6 +1709,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  state_class: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::SensorStateClass>(this->state_class));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  last_reset_type: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::SensorLastResetType>(this->last_reset_type));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,11 @@ enum SensorStateClass : uint32_t {
 | 
			
		||||
  STATE_CLASS_NONE = 0,
 | 
			
		||||
  STATE_CLASS_MEASUREMENT = 1,
 | 
			
		||||
};
 | 
			
		||||
enum SensorLastResetType : uint32_t {
 | 
			
		||||
  LAST_RESET_NONE = 0,
 | 
			
		||||
  LAST_RESET_NEVER = 1,
 | 
			
		||||
  LAST_RESET_AUTO = 2,
 | 
			
		||||
};
 | 
			
		||||
enum LogLevel : uint32_t {
 | 
			
		||||
  LOG_LEVEL_NONE = 0,
 | 
			
		||||
  LOG_LEVEL_ERROR = 1,
 | 
			
		||||
@@ -429,6 +434,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
 | 
			
		||||
  bool force_update{false};
 | 
			
		||||
  std::string device_class{};
 | 
			
		||||
  enums::SensorStateClass state_class{};
 | 
			
		||||
  enums::SensorLastResetType last_reset_type{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ from esphome.const import (
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    ICON_LIGHTBULB,
 | 
			
		||||
    ICON_CURRENT_AC,
 | 
			
		||||
    LAST_RESET_TYPE_AUTO,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_HERTZ,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
@@ -91,10 +92,20 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
 | 
			
		||||
            STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
 | 
			
		||||
            UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT
 | 
			
		||||
            UNIT_WATT_HOURS,
 | 
			
		||||
            ICON_EMPTY,
 | 
			
		||||
            2,
 | 
			
		||||
            DEVICE_CLASS_ENERGY,
 | 
			
		||||
            STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            LAST_RESET_TYPE_AUTO,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
 | 
			
		||||
            UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT
 | 
			
		||||
            UNIT_WATT_HOURS,
 | 
			
		||||
            ICON_EMPTY,
 | 
			
		||||
            2,
 | 
			
		||||
            DEVICE_CLASS_ENERGY,
 | 
			
		||||
            STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            LAST_RESET_TYPE_AUTO,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
 | 
			
		||||
        cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
 | 
			
		||||
 
 | 
			
		||||
@@ -312,8 +312,12 @@ void Climate::add_on_state_callback(std::function<void()> &&callback) {
 | 
			
		||||
  this->state_callback_.add(std::move(callback));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Random 32bit value; If this changes existing restore preferences are invalidated
 | 
			
		||||
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
 | 
			
		||||
 | 
			
		||||
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
 | 
			
		||||
  this->rtc_ = global_preferences.make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash());
 | 
			
		||||
  this->rtc_ =
 | 
			
		||||
      global_preferences.make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^ RESTORE_STATE_VERSION);
 | 
			
		||||
  ClimateDeviceRestoreState recovered{};
 | 
			
		||||
  if (!this->rtc_.load(&recovered))
 | 
			
		||||
    return {};
 | 
			
		||||
 
 | 
			
		||||
@@ -120,6 +120,7 @@ class ClimateCall {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Struct used to save the state of the climate device in restore memory.
 | 
			
		||||
/// Make sure to update RESTORE_STATE_VERSION when changing the struct entries.
 | 
			
		||||
struct ClimateDeviceRestoreState {
 | 
			
		||||
  ClimateMode mode;
 | 
			
		||||
  bool uses_custom_fan_mode{false};
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,9 @@ DisplayPageShowPrevAction = display_ns.class_(
 | 
			
		||||
DisplayIsDisplayingPageCondition = display_ns.class_(
 | 
			
		||||
    "DisplayIsDisplayingPageCondition", automation.Condition
 | 
			
		||||
)
 | 
			
		||||
DisplayOnPageChangeTrigger = display_ns.class_("DisplayOnPageChangeTrigger")
 | 
			
		||||
DisplayOnPageChangeTrigger = display_ns.class_(
 | 
			
		||||
    "DisplayOnPageChangeTrigger", automation.Trigger
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_ON_PAGE_CHANGE = "on_page_change"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ void DutyCycleSensor::setup() {
 | 
			
		||||
  this->store_.pin = this->pin_->to_isr();
 | 
			
		||||
  this->store_.last_level = this->pin_->digital_read();
 | 
			
		||||
  this->last_update_ = micros();
 | 
			
		||||
  this->store_.last_interrupt = micros();
 | 
			
		||||
 | 
			
		||||
  this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, CHANGE);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent {
 | 
			
		||||
 protected:
 | 
			
		||||
  GPIOPin *pin_;
 | 
			
		||||
 | 
			
		||||
  DutyCycleSensorStore store_;
 | 
			
		||||
  DutyCycleSensorStore store_{};
 | 
			
		||||
  uint32_t last_update_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,13 @@ static const char *const TAG = "ethernet";
 | 
			
		||||
 | 
			
		||||
EthernetComponent *global_eth_component;
 | 
			
		||||
 | 
			
		||||
#define ESPHL_ERROR_CHECK(err, message) \
 | 
			
		||||
  if (err != ESP_OK) { \
 | 
			
		||||
    ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \
 | 
			
		||||
    this->mark_failed(); \
 | 
			
		||||
    return; \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
EthernetComponent::EthernetComponent() { global_eth_component = this; }
 | 
			
		||||
void EthernetComponent::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up Ethernet...");
 | 
			
		||||
@@ -36,103 +43,6 @@ void EthernetComponent::setup() {
 | 
			
		||||
    this->power_pin_->setup();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->start_connect_();
 | 
			
		||||
 | 
			
		||||
#ifdef USE_MDNS
 | 
			
		||||
  network_setup_mdns();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
void EthernetComponent::loop() {
 | 
			
		||||
  const uint32_t now = millis();
 | 
			
		||||
  if (!this->connected_ && !this->last_connected_ && now - this->connect_begin_ > 15000) {
 | 
			
		||||
    ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting...");
 | 
			
		||||
    this->start_connect_();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->connected_ == this->last_connected_)
 | 
			
		||||
    // nothing changed
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (this->connected_) {
 | 
			
		||||
    // connection established
 | 
			
		||||
    ESP_LOGI(TAG, "Connected via Ethernet!");
 | 
			
		||||
    this->dump_connect_params_();
 | 
			
		||||
    this->status_clear_warning();
 | 
			
		||||
  } else {
 | 
			
		||||
    // connection lost
 | 
			
		||||
    ESP_LOGW(TAG, "Connection via Ethernet lost! Re-connecting...");
 | 
			
		||||
    this->start_connect_();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->last_connected_ = this->connected_;
 | 
			
		||||
 | 
			
		||||
  network_tick_mdns();
 | 
			
		||||
}
 | 
			
		||||
void EthernetComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Ethernet:");
 | 
			
		||||
  this->dump_connect_params_();
 | 
			
		||||
  LOG_PIN("  Power Pin: ", this->power_pin_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  MDC Pin: %u", this->mdc_pin_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  MDIO Pin: %u", this->mdio_pin_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110");
 | 
			
		||||
}
 | 
			
		||||
float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; }
 | 
			
		||||
bool EthernetComponent::can_proceed() { return this->is_connected(); }
 | 
			
		||||
IPAddress EthernetComponent::get_ip_address() {
 | 
			
		||||
  tcpip_adapter_ip_info_t ip;
 | 
			
		||||
  tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip);
 | 
			
		||||
  return IPAddress(ip.ip.addr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) {
 | 
			
		||||
  const char *event_name;
 | 
			
		||||
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case SYSTEM_EVENT_ETH_START:
 | 
			
		||||
      event_name = "ETH started";
 | 
			
		||||
      break;
 | 
			
		||||
    case SYSTEM_EVENT_ETH_STOP:
 | 
			
		||||
      event_name = "ETH stopped";
 | 
			
		||||
      this->connected_ = false;
 | 
			
		||||
      break;
 | 
			
		||||
    case SYSTEM_EVENT_ETH_CONNECTED:
 | 
			
		||||
      event_name = "ETH connected";
 | 
			
		||||
      break;
 | 
			
		||||
    case SYSTEM_EVENT_ETH_DISCONNECTED:
 | 
			
		||||
      event_name = "ETH disconnected";
 | 
			
		||||
      this->connected_ = false;
 | 
			
		||||
      break;
 | 
			
		||||
    case SYSTEM_EVENT_ETH_GOT_IP:
 | 
			
		||||
      event_name = "ETH Got IP";
 | 
			
		||||
      this->connected_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define ESPHL_ERROR_CHECK(err, message) \
 | 
			
		||||
  if (err != ESP_OK) { \
 | 
			
		||||
    ESP_LOGE(TAG, message ": %d", err); \
 | 
			
		||||
    this->mark_failed(); \
 | 
			
		||||
    return; \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
void EthernetComponent::start_connect_() {
 | 
			
		||||
  this->connect_begin_ = millis();
 | 
			
		||||
  this->status_set_warning();
 | 
			
		||||
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
  if (this->initialized_) {
 | 
			
		||||
    // already initialized
 | 
			
		||||
    err = esp_eth_enable();
 | 
			
		||||
    ESPHL_ERROR_CHECK(err, "ETH enable error");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (this->type_) {
 | 
			
		||||
    case ETHERNET_TYPE_LAN8720: {
 | 
			
		||||
      memcpy(&this->eth_config, &phy_lan8720_default_ethernet_config, sizeof(eth_config_t));
 | 
			
		||||
@@ -160,16 +70,111 @@ void EthernetComponent::start_connect_() {
 | 
			
		||||
 | 
			
		||||
  tcpipInit();
 | 
			
		||||
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
  err = esp_eth_init(&this->eth_config);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "ETH init error: %d", err);
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "ETH init error");
 | 
			
		||||
  err = esp_eth_enable();
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "ETH enable error");
 | 
			
		||||
 | 
			
		||||
#ifdef USE_MDNS
 | 
			
		||||
  network_setup_mdns();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
void EthernetComponent::loop() {
 | 
			
		||||
  const uint32_t now = millis();
 | 
			
		||||
 | 
			
		||||
  switch (this->state_) {
 | 
			
		||||
    case EthernetComponentState::STOPPED:
 | 
			
		||||
      if (this->started_) {
 | 
			
		||||
        ESP_LOGI(TAG, "Starting ethernet connection");
 | 
			
		||||
        this->state_ = EthernetComponentState::CONNECTING;
 | 
			
		||||
        this->start_connect_();
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case EthernetComponentState::CONNECTING:
 | 
			
		||||
      if (!this->started_) {
 | 
			
		||||
        ESP_LOGI(TAG, "Stopped ethernet connection");
 | 
			
		||||
        this->state_ = EthernetComponentState::STOPPED;
 | 
			
		||||
      } else if (this->connected_) {
 | 
			
		||||
        // connection established
 | 
			
		||||
        ESP_LOGI(TAG, "Connected via Ethernet!");
 | 
			
		||||
        this->state_ = EthernetComponentState::CONNECTED;
 | 
			
		||||
 | 
			
		||||
        this->dump_connect_params_();
 | 
			
		||||
        this->status_clear_warning();
 | 
			
		||||
 | 
			
		||||
        network_tick_mdns();
 | 
			
		||||
      } else if (now - this->connect_begin_ > 15000) {
 | 
			
		||||
        ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting...");
 | 
			
		||||
        this->start_connect_();
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case EthernetComponentState::CONNECTED:
 | 
			
		||||
      if (!this->started_) {
 | 
			
		||||
        ESP_LOGI(TAG, "Stopped ethernet connection");
 | 
			
		||||
        this->state_ = EthernetComponentState::STOPPED;
 | 
			
		||||
      } else if (!this->connected_) {
 | 
			
		||||
        ESP_LOGW(TAG, "Connection via Ethernet lost! Re-connecting...");
 | 
			
		||||
        this->state_ = EthernetComponentState::CONNECTING;
 | 
			
		||||
        this->start_connect_();
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void EthernetComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Ethernet:");
 | 
			
		||||
  this->dump_connect_params_();
 | 
			
		||||
  LOG_PIN("  Power Pin: ", this->power_pin_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  MDC Pin: %u", this->mdc_pin_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  MDIO Pin: %u", this->mdio_pin_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110");
 | 
			
		||||
}
 | 
			
		||||
float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; }
 | 
			
		||||
bool EthernetComponent::can_proceed() { return this->is_connected(); }
 | 
			
		||||
IPAddress EthernetComponent::get_ip_address() {
 | 
			
		||||
  tcpip_adapter_ip_info_t ip;
 | 
			
		||||
  tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip);
 | 
			
		||||
  return IPAddress(ip.ip.addr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) {
 | 
			
		||||
  const char *event_name;
 | 
			
		||||
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case SYSTEM_EVENT_ETH_START:
 | 
			
		||||
      event_name = "ETH started";
 | 
			
		||||
      this->started_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    case SYSTEM_EVENT_ETH_STOP:
 | 
			
		||||
      event_name = "ETH stopped";
 | 
			
		||||
      this->started_ = false;
 | 
			
		||||
      this->connected_ = false;
 | 
			
		||||
      break;
 | 
			
		||||
    case SYSTEM_EVENT_ETH_CONNECTED:
 | 
			
		||||
      event_name = "ETH connected";
 | 
			
		||||
      break;
 | 
			
		||||
    case SYSTEM_EVENT_ETH_DISCONNECTED:
 | 
			
		||||
      event_name = "ETH disconnected";
 | 
			
		||||
      this->connected_ = false;
 | 
			
		||||
      break;
 | 
			
		||||
    case SYSTEM_EVENT_ETH_GOT_IP:
 | 
			
		||||
      event_name = "ETH Got IP";
 | 
			
		||||
      this->connected_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->initialized_ = true;
 | 
			
		||||
  ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str());
 | 
			
		||||
void EthernetComponent::start_connect_() {
 | 
			
		||||
  this->connect_begin_ = millis();
 | 
			
		||||
  this->status_set_warning();
 | 
			
		||||
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
  err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str());
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "ETH set hostname error");
 | 
			
		||||
 | 
			
		||||
  tcpip_adapter_ip_info_t info;
 | 
			
		||||
  if (this->manual_ip_.has_value()) {
 | 
			
		||||
@@ -220,7 +225,7 @@ void EthernetComponent::eth_phy_power_enable_(bool enable) {
 | 
			
		||||
  delay(1);
 | 
			
		||||
  global_eth_component->orig_power_enable_fun_(enable);
 | 
			
		||||
}
 | 
			
		||||
bool EthernetComponent::is_connected() { return this->connected_ && this->last_connected_; }
 | 
			
		||||
bool EthernetComponent::is_connected() { return this->state_ == EthernetComponentState::CONNECTED; }
 | 
			
		||||
void EthernetComponent::dump_connect_params_() {
 | 
			
		||||
  tcpip_adapter_ip_info_t ip;
 | 
			
		||||
  tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip);
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,12 @@ struct ManualIP {
 | 
			
		||||
  IPAddress dns2;  ///< The second DNS server. 0.0.0.0 for default.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class EthernetComponentState {
 | 
			
		||||
  STOPPED,
 | 
			
		||||
  CONNECTING,
 | 
			
		||||
  CONNECTED,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class EthernetComponent : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  EthernetComponent();
 | 
			
		||||
@@ -65,9 +71,9 @@ class EthernetComponent : public Component {
 | 
			
		||||
  eth_clock_mode_t clk_mode_{ETH_CLOCK_GPIO0_IN};
 | 
			
		||||
  optional<ManualIP> manual_ip_{};
 | 
			
		||||
 | 
			
		||||
  bool initialized_{false};
 | 
			
		||||
  bool started_{false};
 | 
			
		||||
  bool connected_{false};
 | 
			
		||||
  bool last_connected_{false};
 | 
			
		||||
  EthernetComponentState state_{EthernetComponentState::STOPPED};
 | 
			
		||||
  uint32_t connect_begin_;
 | 
			
		||||
  eth_config_t eth_config;
 | 
			
		||||
  eth_phy_power_enable_func orig_power_enable_fun_;
 | 
			
		||||
 
 | 
			
		||||
@@ -109,7 +109,15 @@ def _compute_destination_path(key: str) -> Path:
 | 
			
		||||
    return base_dir / h.hexdigest()[:8]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _handle_git_response(ret):
 | 
			
		||||
def _run_git_command(cmd, cwd=None):
 | 
			
		||||
    try:
 | 
			
		||||
        ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False)
 | 
			
		||||
    except FileNotFoundError as err:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "git is not installed but required for external_components.\n"
 | 
			
		||||
            "Please see https://git-scm.com/book/en/v2/Getting-Started-Installing-Git for installing git"
 | 
			
		||||
        ) from err
 | 
			
		||||
 | 
			
		||||
    if ret.returncode != 0 and ret.stderr:
 | 
			
		||||
        err_str = ret.stderr.decode("utf-8")
 | 
			
		||||
        lines = [x.strip() for x in err_str.splitlines()]
 | 
			
		||||
@@ -118,46 +126,61 @@ def _handle_git_response(ret):
 | 
			
		||||
        raise cv.Invalid(err_str)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _process_git_config(config: dict, refresh) -> str:
 | 
			
		||||
    key = f"{config[CONF_URL]}@{config.get(CONF_REF)}"
 | 
			
		||||
    repo_dir = _compute_destination_path(key)
 | 
			
		||||
    if not repo_dir.is_dir():
 | 
			
		||||
        _LOGGER.info("Cloning %s", key)
 | 
			
		||||
        _LOGGER.debug("Location: %s", repo_dir)
 | 
			
		||||
        cmd = ["git", "clone", "--depth=1"]
 | 
			
		||||
        if CONF_REF in config:
 | 
			
		||||
            cmd += ["--branch", config[CONF_REF]]
 | 
			
		||||
        cmd += ["--", config[CONF_URL], str(repo_dir)]
 | 
			
		||||
        _run_git_command(cmd)
 | 
			
		||||
 | 
			
		||||
    else:
 | 
			
		||||
        # Check refresh needed
 | 
			
		||||
        file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD")
 | 
			
		||||
        # On first clone, FETCH_HEAD does not exists
 | 
			
		||||
        if not file_timestamp.exists():
 | 
			
		||||
            file_timestamp = Path(repo_dir / ".git" / "HEAD")
 | 
			
		||||
        age = datetime.datetime.now() - datetime.datetime.fromtimestamp(
 | 
			
		||||
            file_timestamp.stat().st_mtime
 | 
			
		||||
        )
 | 
			
		||||
        if age.total_seconds() > refresh.total_seconds:
 | 
			
		||||
            _LOGGER.info("Updating %s", key)
 | 
			
		||||
            _LOGGER.debug("Location: %s", repo_dir)
 | 
			
		||||
            # Stash local changes (if any)
 | 
			
		||||
            _run_git_command(
 | 
			
		||||
                ["git", "stash", "push", "--include-untracked"], str(repo_dir)
 | 
			
		||||
            )
 | 
			
		||||
            # Fetch remote ref
 | 
			
		||||
            cmd = ["git", "fetch", "--", "origin"]
 | 
			
		||||
            if CONF_REF in config:
 | 
			
		||||
                cmd.append(config[CONF_REF])
 | 
			
		||||
            _run_git_command(cmd, str(repo_dir))
 | 
			
		||||
            # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch)
 | 
			
		||||
            _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir))
 | 
			
		||||
 | 
			
		||||
    if (repo_dir / "esphome" / "components").is_dir():
 | 
			
		||||
        components_dir = repo_dir / "esphome" / "components"
 | 
			
		||||
    elif (repo_dir / "components").is_dir():
 | 
			
		||||
        components_dir = repo_dir / "components"
 | 
			
		||||
    else:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "Could not find components folder for source. Please check the source contains a 'components' or 'esphome/components' folder"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return components_dir
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _process_single_config(config: dict):
 | 
			
		||||
    conf = config[CONF_SOURCE]
 | 
			
		||||
    if conf[CONF_TYPE] == TYPE_GIT:
 | 
			
		||||
        key = f"{conf[CONF_URL]}@{conf.get(CONF_REF)}"
 | 
			
		||||
        repo_dir = _compute_destination_path(key)
 | 
			
		||||
        if not repo_dir.is_dir():
 | 
			
		||||
            cmd = ["git", "clone", "--depth=1"]
 | 
			
		||||
            if CONF_REF in conf:
 | 
			
		||||
                cmd += ["--branch", conf[CONF_REF]]
 | 
			
		||||
            cmd += [conf[CONF_URL], str(repo_dir)]
 | 
			
		||||
            ret = subprocess.run(cmd, capture_output=True, check=False)
 | 
			
		||||
            _handle_git_response(ret)
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            # Check refresh needed
 | 
			
		||||
            file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD")
 | 
			
		||||
            # On first clone, FETCH_HEAD does not exists
 | 
			
		||||
            if not file_timestamp.exists():
 | 
			
		||||
                file_timestamp = Path(repo_dir / ".git" / "HEAD")
 | 
			
		||||
            age = datetime.datetime.now() - datetime.datetime.fromtimestamp(
 | 
			
		||||
                file_timestamp.stat().st_mtime
 | 
			
		||||
        with cv.prepend_path([CONF_SOURCE]):
 | 
			
		||||
            components_dir = _process_git_config(
 | 
			
		||||
                config[CONF_SOURCE], config[CONF_REFRESH]
 | 
			
		||||
            )
 | 
			
		||||
            if age.seconds > config[CONF_REFRESH].total_seconds:
 | 
			
		||||
                _LOGGER.info("Executing git pull %s", key)
 | 
			
		||||
                cmd = ["git", "pull"]
 | 
			
		||||
                ret = subprocess.run(
 | 
			
		||||
                    cmd, cwd=repo_dir, capture_output=True, check=False
 | 
			
		||||
                )
 | 
			
		||||
                _handle_git_response(ret)
 | 
			
		||||
 | 
			
		||||
        if (repo_dir / "esphome" / "components").is_dir():
 | 
			
		||||
            components_dir = repo_dir / "esphome" / "components"
 | 
			
		||||
        elif (repo_dir / "components").is_dir():
 | 
			
		||||
            components_dir = repo_dir / "components"
 | 
			
		||||
        else:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                "Could not find components folder for source. Please check the source contains a 'components' or 'esphome/components' folder",
 | 
			
		||||
                [CONF_SOURCE],
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    elif conf[CONF_TYPE] == TYPE_LOCAL:
 | 
			
		||||
        components_dir = Path(CORE.relative_config_path(conf[CONF_PATH]))
 | 
			
		||||
    else:
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ICON_CURRENT_AC,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    LAST_RESET_TYPE_AUTO,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    STATE_CLASS_NONE,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
@@ -121,14 +122,16 @@ CONFIG_SCHEMA = (
 | 
			
		||||
                ICON_EMPTY,
 | 
			
		||||
                2,
 | 
			
		||||
                DEVICE_CLASS_ENERGY,
 | 
			
		||||
                STATE_CLASS_NONE,
 | 
			
		||||
                STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                LAST_RESET_TYPE_AUTO,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema(
 | 
			
		||||
                UNIT_KILOWATT_HOURS,
 | 
			
		||||
                ICON_EMPTY,
 | 
			
		||||
                0,
 | 
			
		||||
                DEVICE_CLASS_ENERGY,
 | 
			
		||||
                STATE_CLASS_NONE,
 | 
			
		||||
                STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                LAST_RESET_TYPE_AUTO,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TOTAL_GENERATION_TIME): sensor.sensor_schema(
 | 
			
		||||
                UNIT_HOURS,
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@ void HLW8012Component::dump_config() {
 | 
			
		||||
  LOG_SENSOR("  ", "Voltage", this->voltage_sensor_)
 | 
			
		||||
  LOG_SENSOR("  ", "Current", this->current_sensor_)
 | 
			
		||||
  LOG_SENSOR("  ", "Power", this->power_sensor_)
 | 
			
		||||
  LOG_SENSOR("  ", "Energy", this->energy_sensor_)
 | 
			
		||||
}
 | 
			
		||||
float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
void HLW8012Component::update() {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,8 +19,8 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    LAST_RESET_TYPE_AUTO,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    STATE_CLASS_NONE,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
@@ -67,7 +67,12 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
            UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ENERGY): sensor.sensor_schema(
 | 
			
		||||
            UNIT_WATT_HOURS, ICON_EMPTY, 1, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE
 | 
			
		||||
            UNIT_WATT_HOURS,
 | 
			
		||||
            ICON_EMPTY,
 | 
			
		||||
            1,
 | 
			
		||||
            DEVICE_CLASS_ENERGY,
 | 
			
		||||
            STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            LAST_RESET_TYPE_AUTO,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance,
 | 
			
		||||
        cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float,
 | 
			
		||||
 
 | 
			
		||||
@@ -68,10 +68,7 @@ void AddressableLight::write_state(LightState *state) {
 | 
			
		||||
 | 
			
		||||
    // our transition will handle brightness, disable brightness in correction.
 | 
			
		||||
    this->correction_.set_local_brightness(255);
 | 
			
		||||
    uint8_t orig_w = target_color.w;
 | 
			
		||||
    target_color *= static_cast<uint8_t>(roundf(end_values.get_brightness() * end_values.get_state() * 255.0f));
 | 
			
		||||
    // w is not scaled by brightness
 | 
			
		||||
    target_color.w = orig_w;
 | 
			
		||||
 | 
			
		||||
    float denom = (1.0f - new_smoothed);
 | 
			
		||||
    float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom;
 | 
			
		||||
 
 | 
			
		||||
@@ -100,7 +100,7 @@ bool MideaAC::allow_preset(climate::ClimatePreset preset) const {
 | 
			
		||||
        ESP_LOGD(TAG, "BOOST preset is only available in HEAT or COOL mode");
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_PRESET_HOME:
 | 
			
		||||
    case climate::CLIMATE_PRESET_NONE:
 | 
			
		||||
      return true;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
@@ -191,7 +191,7 @@ climate::ClimateTraits MideaAC::traits() {
 | 
			
		||||
  if (traits_swing_both_)
 | 
			
		||||
    traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH);
 | 
			
		||||
  traits.set_supported_presets({
 | 
			
		||||
      climate::CLIMATE_PRESET_HOME,
 | 
			
		||||
      climate::CLIMATE_PRESET_NONE,
 | 
			
		||||
  });
 | 
			
		||||
  if (traits_preset_eco_)
 | 
			
		||||
    traits.add_supported_preset(climate::CLIMATE_PRESET_ECO);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,9 @@
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/midea_dongle/midea_dongle.h"
 | 
			
		||||
#include "esphome/components/climate/climate.h"
 | 
			
		||||
#include "esphome/components/midea_dongle/midea_dongle.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "midea_frame.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
 
 | 
			
		||||
@@ -86,18 +86,17 @@ void PropertiesFrame::set_mode(climate::ClimateMode mode) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
optional<climate::ClimatePreset> PropertiesFrame::get_preset() const {
 | 
			
		||||
  if (this->get_eco_mode()) {
 | 
			
		||||
  if (this->get_eco_mode())
 | 
			
		||||
    return climate::CLIMATE_PRESET_ECO;
 | 
			
		||||
  } else if (this->get_sleep_mode()) {
 | 
			
		||||
  if (this->get_sleep_mode())
 | 
			
		||||
    return climate::CLIMATE_PRESET_SLEEP;
 | 
			
		||||
  } else if (this->get_turbo_mode()) {
 | 
			
		||||
  if (this->get_turbo_mode())
 | 
			
		||||
    return climate::CLIMATE_PRESET_BOOST;
 | 
			
		||||
  } else {
 | 
			
		||||
    return climate::CLIMATE_PRESET_HOME;
 | 
			
		||||
  }
 | 
			
		||||
  return climate::CLIMATE_PRESET_NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PropertiesFrame::set_preset(climate::ClimatePreset preset) {
 | 
			
		||||
  this->clear_presets();
 | 
			
		||||
  switch (preset) {
 | 
			
		||||
    case climate::CLIMATE_PRESET_ECO:
 | 
			
		||||
      this->set_eco_mode(true);
 | 
			
		||||
@@ -113,14 +112,21 @@ void PropertiesFrame::set_preset(climate::ClimatePreset preset) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PropertiesFrame::clear_presets() {
 | 
			
		||||
  this->set_eco_mode(false);
 | 
			
		||||
  this->set_sleep_mode(false);
 | 
			
		||||
  this->set_turbo_mode(false);
 | 
			
		||||
  this->set_freeze_protection_mode(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PropertiesFrame::is_custom_preset() const { return this->get_freeze_protection_mode(); }
 | 
			
		||||
 | 
			
		||||
const std::string &PropertiesFrame::get_custom_preset() const { return midea_ac::MIDEA_FREEZE_PROTECTION_PRESET; };
 | 
			
		||||
 | 
			
		||||
void PropertiesFrame::set_custom_preset(const std::string &preset) {
 | 
			
		||||
  if (preset == MIDEA_FREEZE_PROTECTION_PRESET) {
 | 
			
		||||
  this->clear_presets();
 | 
			
		||||
  if (preset == MIDEA_FREEZE_PROTECTION_PRESET)
 | 
			
		||||
    this->set_freeze_protection_mode(true);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PropertiesFrame::is_custom_fan_mode() const {
 | 
			
		||||
 
 | 
			
		||||
@@ -102,8 +102,11 @@ class PropertiesFrame : public midea_dongle::BaseFrame {
 | 
			
		||||
  void set_sleep_mode(bool state) { this->set_bytemask_(20, 0x01, state); }
 | 
			
		||||
 | 
			
		||||
  /* TURBO MODE */
 | 
			
		||||
  bool get_turbo_mode() const { return this->pbuf_[18] & 0x20; }
 | 
			
		||||
  void set_turbo_mode(bool state) { this->set_bytemask_(18, 0x20, state); }
 | 
			
		||||
  bool get_turbo_mode() const { return this->pbuf_[18] & 0x20 || this->pbuf_[20] & 0x02; }
 | 
			
		||||
  void set_turbo_mode(bool state) {
 | 
			
		||||
    this->set_bytemask_(18, 0x20, state);
 | 
			
		||||
    this->set_bytemask_(20, 0x02, state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* FREEZE PROTECTION */
 | 
			
		||||
  bool get_freeze_protection_mode() const { return this->pbuf_[31] & 0x80; }
 | 
			
		||||
@@ -112,6 +115,7 @@ class PropertiesFrame : public midea_dongle::BaseFrame {
 | 
			
		||||
  /* PRESET */
 | 
			
		||||
  optional<climate::ClimatePreset> get_preset() const;
 | 
			
		||||
  void set_preset(climate::ClimatePreset preset);
 | 
			
		||||
  void clear_presets();
 | 
			
		||||
 | 
			
		||||
  bool is_custom_preset() const;
 | 
			
		||||
  const std::string &get_custom_preset() const;
 | 
			
		||||
 
 | 
			
		||||
@@ -72,7 +72,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
 | 
			
		||||
    root["act_t"] = this->get_action_state_topic();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (traits.get_supports_fan_modes()) {
 | 
			
		||||
  if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
 | 
			
		||||
    // fan_mode_command_topic
 | 
			
		||||
    root["fan_mode_cmd_t"] = this->get_fan_mode_command_topic();
 | 
			
		||||
    // fan_mode_state_topic
 | 
			
		||||
 
 | 
			
		||||
@@ -3,10 +3,6 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
 | 
			
		||||
#ifdef USE_DEEP_SLEEP
 | 
			
		||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mqtt {
 | 
			
		||||
 | 
			
		||||
@@ -20,7 +16,7 @@ void MQTTNumberComponent::setup() {
 | 
			
		||||
  this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) {
 | 
			
		||||
    auto val = parse_float(state);
 | 
			
		||||
    if (!val.has_value()) {
 | 
			
		||||
      ESP_LOGE(TAG, "Can't convert '%s' to number!", state.c_str());
 | 
			
		||||
      ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str());
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    auto call = this->number_->make_call();
 | 
			
		||||
@@ -39,8 +35,15 @@ std::string MQTTNumberComponent::component_type() const { return "number"; }
 | 
			
		||||
 | 
			
		||||
std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); }
 | 
			
		||||
void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
 | 
			
		||||
  if (!this->number_->get_icon().empty())
 | 
			
		||||
    root["icon"] = this->number_->get_icon();
 | 
			
		||||
  const auto &traits = number_->traits;
 | 
			
		||||
  // https://www.home-assistant.io/integrations/number.mqtt/
 | 
			
		||||
  if (!traits.get_icon().empty())
 | 
			
		||||
    root["icon"] = traits.get_icon();
 | 
			
		||||
  root["min"] = traits.get_min_value();
 | 
			
		||||
  root["max"] = traits.get_max_value();
 | 
			
		||||
  root["step"] = traits.get_step();
 | 
			
		||||
 | 
			
		||||
  config.command_topic = true;
 | 
			
		||||
}
 | 
			
		||||
bool MQTTNumberComponent::send_initial_state() {
 | 
			
		||||
  if (this->number_->has_state()) {
 | 
			
		||||
@@ -51,8 +54,9 @@ bool MQTTNumberComponent::send_initial_state() {
 | 
			
		||||
}
 | 
			
		||||
bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); }
 | 
			
		||||
bool MQTTNumberComponent::publish_state(float value) {
 | 
			
		||||
  int8_t accuracy = this->number_->get_accuracy_decimals();
 | 
			
		||||
  return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy));
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  snprintf(buffer, sizeof(buffer), "%f", value);
 | 
			
		||||
  return this->publish(this->get_state_topic_(), buffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace mqtt
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,8 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import uart
 | 
			
		||||
 | 
			
		||||
nextion_ns = cg.esphome_ns.namespace("nextion")
 | 
			
		||||
Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice)
 | 
			
		||||
nextion_ref = Nextion.operator("ref")
 | 
			
		||||
 | 
			
		||||
CONF_NEXTION_ID = "nextion_id"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								esphome/components/nextion/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								esphome/components/nextion/automation.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "nextion.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
 | 
			
		||||
class SetupTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit SetupTrigger(Nextion *nextion) {
 | 
			
		||||
    nextion->add_setup_state_callback([this]() { this->trigger(); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SleepTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit SleepTrigger(Nextion *nextion) {
 | 
			
		||||
    nextion->add_sleep_state_callback([this]() { this->trigger(); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class WakeTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit WakeTrigger(Nextion *nextion) {
 | 
			
		||||
    nextion->add_wake_state_callback([this]() { this->trigger(); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										126
									
								
								esphome/components/nextion/base_component.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								esphome/components/nextion/base_component.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,126 @@
 | 
			
		||||
from string import ascii_letters, digits
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import color
 | 
			
		||||
 | 
			
		||||
from . import CONF_NEXTION_ID
 | 
			
		||||
from . import Nextion
 | 
			
		||||
 | 
			
		||||
CONF_VARIABLE_NAME = "variable_name"
 | 
			
		||||
CONF_COMPONENT_NAME = "component_name"
 | 
			
		||||
CONF_WAVE_CHANNEL_ID = "wave_channel_id"
 | 
			
		||||
CONF_WAVE_MAX_VALUE = "wave_max_value"
 | 
			
		||||
CONF_PRECISION = "precision"
 | 
			
		||||
CONF_WAVEFORM_SEND_LAST_VALUE = "waveform_send_last_value"
 | 
			
		||||
CONF_TFT_URL = "tft_url"
 | 
			
		||||
CONF_ON_SLEEP = "on_sleep"
 | 
			
		||||
CONF_ON_WAKE = "on_wake"
 | 
			
		||||
CONF_ON_SETUP = "on_setup"
 | 
			
		||||
CONF_TOUCH_SLEEP_TIMEOUT = "touch_sleep_timeout"
 | 
			
		||||
CONF_WAKE_UP_PAGE = "wake_up_page"
 | 
			
		||||
CONF_AUTO_WAKE_ON_TOUCH = "auto_wake_on_touch"
 | 
			
		||||
CONF_WAVE_MAX_LENGTH = "wave_max_length"
 | 
			
		||||
CONF_BACKGROUND_COLOR = "background_color"
 | 
			
		||||
CONF_BACKGROUND_PRESSED_COLOR = "background_pressed_color"
 | 
			
		||||
CONF_FOREGROUND_COLOR = "foreground_color"
 | 
			
		||||
CONF_FOREGROUND_PRESSED_COLOR = "foreground_pressed_color"
 | 
			
		||||
CONF_FONT_ID = "font_id"
 | 
			
		||||
CONF_VISIBLE = "visible"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def NextionName(value):
 | 
			
		||||
    valid_chars = ascii_letters + digits + "."
 | 
			
		||||
    if not isinstance(value, str) or len(value) > 29:
 | 
			
		||||
        raise cv.Invalid("Must be a string less than 29 characters")
 | 
			
		||||
 | 
			
		||||
    for char in value:
 | 
			
		||||
        if char not in valid_chars:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                "Must only consist of upper/lowercase characters, numbers and the period '.'. The character '{}' cannot be used.".format(
 | 
			
		||||
                    char
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_BASE_COMPONENT_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion),
 | 
			
		||||
        cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color),
 | 
			
		||||
        cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color),
 | 
			
		||||
        cv.Optional(CONF_VISIBLE, default=True): cv.boolean,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_TEXT_COMPONENT_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_COMPONENT_NAME): NextionName,
 | 
			
		||||
            cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_BINARY_SENSOR_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_COMPONENT_NAME): NextionName,
 | 
			
		||||
            cv.Optional(CONF_VARIABLE_NAME): NextionName,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SENSOR_COMPONENT_SCHEMA = CONFIG_BINARY_SENSOR_SCHEMA.extend(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SWITCH_COMPONENT_SCHEMA = CONFIG_SENSOR_COMPONENT_SCHEMA.extend(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_FOREGROUND_PRESSED_COLOR): cv.use_id(color),
 | 
			
		||||
            cv.Optional(CONF_BACKGROUND_PRESSED_COLOR): cv.use_id(color),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_component_core_(var, config, arg):
 | 
			
		||||
 | 
			
		||||
    if CONF_VARIABLE_NAME in config:
 | 
			
		||||
        cg.add(var.set_variable_name(config[CONF_VARIABLE_NAME]))
 | 
			
		||||
    elif CONF_COMPONENT_NAME in config:
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_variable_name(
 | 
			
		||||
                config[CONF_COMPONENT_NAME],
 | 
			
		||||
                config[CONF_COMPONENT_NAME] + arg,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if CONF_BACKGROUND_COLOR in config:
 | 
			
		||||
        color_component = await cg.get_variable(config[CONF_BACKGROUND_COLOR])
 | 
			
		||||
        cg.add(var.set_background_color(color_component))
 | 
			
		||||
 | 
			
		||||
    if CONF_BACKGROUND_PRESSED_COLOR in config:
 | 
			
		||||
        color_component = await cg.get_variable(config[CONF_BACKGROUND_PRESSED_COLOR])
 | 
			
		||||
        cg.add(var.set_background_pressed_color(color_component))
 | 
			
		||||
 | 
			
		||||
    if CONF_FOREGROUND_COLOR in config:
 | 
			
		||||
        color_component = await cg.get_variable(config[CONF_FOREGROUND_COLOR])
 | 
			
		||||
        cg.add(var.set_foreground_color(color_component))
 | 
			
		||||
 | 
			
		||||
    if CONF_FOREGROUND_PRESSED_COLOR in config:
 | 
			
		||||
        color_component = await cg.get_variable(config[CONF_FOREGROUND_PRESSED_COLOR])
 | 
			
		||||
        cg.add(var.set_foreground_pressed_color(color_component))
 | 
			
		||||
 | 
			
		||||
    if CONF_FONT_ID in config:
 | 
			
		||||
        cg.add(var.set_font_id(config[CONF_FONT_ID]))
 | 
			
		||||
 | 
			
		||||
    if CONF_VISIBLE in config:
 | 
			
		||||
        cg.add(var.set_visible(config[CONF_VISIBLE]))
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import binary_sensor
 | 
			
		||||
from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID
 | 
			
		||||
from . import nextion_ns
 | 
			
		||||
from .display import Nextion
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["display"]
 | 
			
		||||
 | 
			
		||||
CONF_NEXTION_ID = "nextion_id"
 | 
			
		||||
 | 
			
		||||
NextionTouchComponent = nextion_ns.class_(
 | 
			
		||||
    "NextionTouchComponent", binary_sensor.BinarySensor
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(NextionTouchComponent),
 | 
			
		||||
        cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion),
 | 
			
		||||
        cv.Required(CONF_PAGE_ID): cv.uint8_t,
 | 
			
		||||
        cv.Required(CONF_COMPONENT_ID): cv.uint8_t,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await binary_sensor.register_binary_sensor(var, config)
 | 
			
		||||
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_NEXTION_ID])
 | 
			
		||||
    cg.add(hub.register_touch_component(var))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_component_id(config[CONF_COMPONENT_ID]))
 | 
			
		||||
    cg.add(var.set_page_id(config[CONF_PAGE_ID]))
 | 
			
		||||
							
								
								
									
										54
									
								
								esphome/components/nextion/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								esphome/components/nextion/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import binary_sensor
 | 
			
		||||
 | 
			
		||||
from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID
 | 
			
		||||
from .. import nextion_ns, CONF_NEXTION_ID
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from ..base_component import (
 | 
			
		||||
    setup_component_core_,
 | 
			
		||||
    CONFIG_BINARY_SENSOR_SCHEMA,
 | 
			
		||||
    CONF_VARIABLE_NAME,
 | 
			
		||||
    CONF_COMPONENT_NAME,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@senexcrenshaw"]
 | 
			
		||||
 | 
			
		||||
NextionBinarySensor = nextion_ns.class_(
 | 
			
		||||
    "NextionBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    binary_sensor.BINARY_SENSOR_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(NextionBinarySensor),
 | 
			
		||||
            cv.Optional(CONF_PAGE_ID): cv.uint8_t,
 | 
			
		||||
            cv.Optional(CONF_COMPONENT_ID): cv.uint8_t,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(CONFIG_BINARY_SENSOR_SCHEMA)
 | 
			
		||||
    .extend(cv.polling_component_schema("never")),
 | 
			
		||||
    cv.has_at_least_one_key(
 | 
			
		||||
        CONF_PAGE_ID,
 | 
			
		||||
        CONF_COMPONENT_ID,
 | 
			
		||||
        CONF_COMPONENT_NAME,
 | 
			
		||||
        CONF_VARIABLE_NAME,
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_NEXTION_ID])
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], hub)
 | 
			
		||||
    await binary_sensor.register_binary_sensor(var, config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    if config.keys() >= {CONF_PAGE_ID, CONF_COMPONENT_ID}:
 | 
			
		||||
        cg.add(hub.register_touch_component(var))
 | 
			
		||||
        cg.add(var.set_component_id(config[CONF_COMPONENT_ID]))
 | 
			
		||||
        cg.add(var.set_page_id(config[CONF_PAGE_ID]))
 | 
			
		||||
 | 
			
		||||
    if CONF_COMPONENT_NAME in config or CONF_VARIABLE_NAME in config:
 | 
			
		||||
        await setup_component_core_(var, config, ".val")
 | 
			
		||||
        cg.add(hub.register_binarysensor_component(var))
 | 
			
		||||
@@ -0,0 +1,69 @@
 | 
			
		||||
#include "nextion_binarysensor.h"
 | 
			
		||||
#include "esphome/core/util.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "nextion_binarysensor";
 | 
			
		||||
 | 
			
		||||
void NextionBinarySensor::process_bool(const std::string &variable_name, bool state) {
 | 
			
		||||
  if (!this->nextion_->is_setup())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (this->variable_name_.empty())  // This is a touch component
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (this->variable_name_ == variable_name) {
 | 
			
		||||
    this->publish_state(state);
 | 
			
		||||
    ESP_LOGD(TAG, "Processed binarysensor \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionBinarySensor::process_touch(uint8_t page_id, uint8_t component_id, bool state) {
 | 
			
		||||
  if (this->page_id_ == page_id && this->component_id_ == component_id) {
 | 
			
		||||
    this->publish_state(state);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionBinarySensor::update() {
 | 
			
		||||
  if (!this->nextion_->is_setup())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (this->variable_name_.empty())  // This is a touch component
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  this->nextion_->add_to_get_queue(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nextion) {
 | 
			
		||||
  if (!this->nextion_->is_setup())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (this->component_id_ == 0)  // This is a legacy touch component
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (send_to_nextion) {
 | 
			
		||||
    if (this->nextion_->is_sleeping() || !this->visible_) {
 | 
			
		||||
      this->needs_to_send_update_ = true;
 | 
			
		||||
    } else {
 | 
			
		||||
      this->needs_to_send_update_ = false;
 | 
			
		||||
      this->nextion_->add_no_result_to_queue_with_set(this, (int) state);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (publish) {
 | 
			
		||||
    this->publish_state(state);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->state = state;
 | 
			
		||||
    this->has_state_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->update_component_settings();
 | 
			
		||||
 | 
			
		||||
  ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %s", this->variable_name_.c_str(),
 | 
			
		||||
           ONOFF(this->variable_name_.c_str()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -0,0 +1,42 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
#include "../nextion_component.h"
 | 
			
		||||
#include "../nextion_base.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
class NextionBinarySensor;
 | 
			
		||||
 | 
			
		||||
class NextionBinarySensor : public NextionComponent,
 | 
			
		||||
                            public binary_sensor::BinarySensorInitiallyOff,
 | 
			
		||||
                            public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  NextionBinarySensor(NextionBase *nextion) { this->nextion_ = nextion; }
 | 
			
		||||
 | 
			
		||||
  void update_component() override { this->update(); }
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void send_state_to_nextion() override { this->set_state(this->state, false); };
 | 
			
		||||
  void process_bool(const std::string &variable_name, bool state) override;
 | 
			
		||||
  void process_touch(uint8_t page_id, uint8_t component_id, bool state) override;
 | 
			
		||||
 | 
			
		||||
  // Set the components page id for Nextion Touch Component
 | 
			
		||||
  void set_page_id(uint8_t page_id) { page_id_ = page_id; }
 | 
			
		||||
  // Set the components component id for Nextion Touch Component
 | 
			
		||||
  void set_component_id(uint8_t component_id) { component_id_ = component_id; }
 | 
			
		||||
 | 
			
		||||
  void set_state(bool state) override { this->set_state(state, true, true); }
 | 
			
		||||
  void set_state(bool state, bool publish) override { this->set_state(state, publish, true); }
 | 
			
		||||
  void set_state(bool state, bool publish, bool send_to_nextion) override;
 | 
			
		||||
 | 
			
		||||
  NextionQueueType get_queue_type() override { return NextionQueueType::BINARY_SENSOR; }
 | 
			
		||||
  void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {}
 | 
			
		||||
  void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {
 | 
			
		||||
    this->set_state(state_value != 0, publish, send_to_nextion);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t page_id_;
 | 
			
		||||
};
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,20 +1,58 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.components import display, uart
 | 
			
		||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_BRIGHTNESS
 | 
			
		||||
from . import nextion_ns
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_LAMBDA,
 | 
			
		||||
    CONF_BRIGHTNESS,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from . import Nextion, nextion_ns, nextion_ref
 | 
			
		||||
from .base_component import (
 | 
			
		||||
    CONF_ON_SLEEP,
 | 
			
		||||
    CONF_ON_WAKE,
 | 
			
		||||
    CONF_ON_SETUP,
 | 
			
		||||
    CONF_TFT_URL,
 | 
			
		||||
    CONF_TOUCH_SLEEP_TIMEOUT,
 | 
			
		||||
    CONF_WAKE_UP_PAGE,
 | 
			
		||||
    CONF_AUTO_WAKE_ON_TOUCH,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@senexcrenshaw"]
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["uart"]
 | 
			
		||||
AUTO_LOAD = ["binary_sensor"]
 | 
			
		||||
AUTO_LOAD = ["binary_sensor", "switch", "sensor", "text_sensor"]
 | 
			
		||||
 | 
			
		||||
Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice)
 | 
			
		||||
NextionRef = Nextion.operator("ref")
 | 
			
		||||
SetupTrigger = nextion_ns.class_("SetupTrigger", automation.Trigger.template())
 | 
			
		||||
SleepTrigger = nextion_ns.class_("SleepTrigger", automation.Trigger.template())
 | 
			
		||||
WakeTrigger = nextion_ns.class_("WakeTrigger", automation.Trigger.template())
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    display.BASIC_DISPLAY_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Nextion),
 | 
			
		||||
            cv.Optional(CONF_TFT_URL): cv.string,
 | 
			
		||||
            cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
 | 
			
		||||
            cv.Optional(CONF_ON_SETUP): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SetupTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_SLEEP): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SleepTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_WAKE): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WakeTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TOUCH_SLEEP_TIMEOUT): cv.int_range(min=3, max=65535),
 | 
			
		||||
            cv.Optional(CONF_WAKE_UP_PAGE): cv.positive_int,
 | 
			
		||||
            cv.Optional(CONF_AUTO_WAKE_ON_TOUCH, default=True): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("5s"))
 | 
			
		||||
@@ -31,8 +69,33 @@ async def to_code(config):
 | 
			
		||||
        cg.add(var.set_brightness(config[CONF_BRIGHTNESS]))
 | 
			
		||||
    if CONF_LAMBDA in config:
 | 
			
		||||
        lambda_ = await cg.process_lambda(
 | 
			
		||||
            config[CONF_LAMBDA], [(NextionRef, "it")], return_type=cg.void
 | 
			
		||||
            config[CONF_LAMBDA], [(nextion_ref, "it")], return_type=cg.void
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_writer(lambda_))
 | 
			
		||||
 | 
			
		||||
    if CONF_TFT_URL in config:
 | 
			
		||||
        cg.add_define("USE_TFT_UPLOAD")
 | 
			
		||||
        cg.add(var.set_tft_url(config[CONF_TFT_URL]))
 | 
			
		||||
 | 
			
		||||
    if CONF_TOUCH_SLEEP_TIMEOUT in config:
 | 
			
		||||
        cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT]))
 | 
			
		||||
 | 
			
		||||
    if CONF_WAKE_UP_PAGE in config:
 | 
			
		||||
        cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE]))
 | 
			
		||||
 | 
			
		||||
    if CONF_AUTO_WAKE_ON_TOUCH in config:
 | 
			
		||||
        cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH]))
 | 
			
		||||
 | 
			
		||||
    await display.register_display(var, config)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_SETUP, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_SLEEP, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_WAKE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,9 +1,21 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include <deque>
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/components/uart/uart.h"
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
#include "nextion_base.h"
 | 
			
		||||
#include "nextion_component.h"
 | 
			
		||||
#include "esphome/components/display/display_color_utils.h"
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ETHERNET) || defined(USE_WIFI)
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
#include <HTTPClient.h>
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP8266
 | 
			
		||||
#include <ESP8266HTTPClient.h>
 | 
			
		||||
#include <WiFiClientSecure.h>
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TIME
 | 
			
		||||
#include "esphome/components/time/real_time_clock.h"
 | 
			
		||||
@@ -12,12 +24,14 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
 | 
			
		||||
class NextionTouchComponent;
 | 
			
		||||
class Nextion;
 | 
			
		||||
class NextionComponentBase;
 | 
			
		||||
 | 
			
		||||
using nextion_writer_t = std::function<void(Nextion &)>;
 | 
			
		||||
 | 
			
		||||
class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
static const std::string COMMAND_DELIMITER{static_cast<char>(255), static_cast<char>(255), static_cast<char>(255)};
 | 
			
		||||
 | 
			
		||||
class Nextion : public NextionBase, public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the text of a component to a static string.
 | 
			
		||||
@@ -73,9 +87,20 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the image of the component `pic` to the image with ID `4`.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_picture(const char *component, const char *picture) {
 | 
			
		||||
    this->send_command_printf("%s.val=%s", component, picture);
 | 
			
		||||
  }
 | 
			
		||||
  void set_component_picture(const char *component, const char *picture);
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the background color of a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
   * @param color The color (as a uint32_t).
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_background_color("button", 0xFF0000);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the background color of the component `button` to red.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_background_color(const char *component, uint32_t color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the background color of a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
@@ -83,7 +108,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_background_color("button", "17013");
 | 
			
		||||
   * it.set_component_background_color("button", "RED");
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the background color of the component `button` to blue.
 | 
			
		||||
@@ -91,6 +116,33 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   * Nextion HMI colors.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_background_color(const char *component, const char *color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the background color of a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
   * @param color The color (as Color).
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_background_color("button", color);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the background color of the component `button` to what color contains.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_background_color(const char *component, Color color) override;
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the pressed background color of a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
   * @param color The color (as a int).
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_pressed_background_color("button", 0xFF0000 );
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the pressed background color of the component `button` to red. This is the background color that
 | 
			
		||||
   * is shown when the component is pressed.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_pressed_background_color(const char *component, uint32_t color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the pressed background color of a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
@@ -98,7 +150,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_pressed_background_color("button", "17013");
 | 
			
		||||
   * it.set_component_pressed_background_color("button", "RED");
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the pressed background color of the component `button` to blue. This is the background color that
 | 
			
		||||
@@ -107,6 +159,63 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   * colors.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_pressed_background_color(const char *component, const char *color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the pressed background color of a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
   * @param color The color (as Color).
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_pressed_background_color("button", color);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the pressed background color of the component `button` to blue. This is the background color that
 | 
			
		||||
   * is shown when the component is pressed. Use this [color
 | 
			
		||||
   * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI
 | 
			
		||||
   * colors.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_pressed_background_color(const char *component, Color color) override;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the picture id of a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
   * @param pic_id The picture ID.
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_pic("textview", 1);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the picture id of the component `textview`.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_pic(const char *component, uint8_t pic_id);
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the background picture id of component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
   * @param pic_id The picture ID.
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_picc("textview", 1);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the background picture id of the component `textview`.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_picc(const char *component, uint8_t pic_id);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the font color of a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
   * @param color The color (as a uint32_t ).
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_font_color("textview", 0xFF0000);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the font color of the component `textview` to a red color.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_font_color(const char *component, uint32_t color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the font color of a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
@@ -114,7 +223,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_font_color("textview", "17013");
 | 
			
		||||
   * it.set_component_font_color("textview", "RED");
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the font color of the component `textview` to a blue color.
 | 
			
		||||
@@ -122,6 +231,34 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   * Nextion HMI colors.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_font_color(const char *component, const char *color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the font color of a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
   * @param color The color (as Color).
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_font_color("textview", color);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the font color of the component `textview` to a blue color.
 | 
			
		||||
   * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to
 | 
			
		||||
   * Nextion HMI colors.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_font_color(const char *component, Color color) override;
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the pressed font color of a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
   * @param color The color (as a uint32_t).
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_pressed_font_color("button", 0xFF0000);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the pressed font color of the component `button` to a red.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_pressed_font_color(const char *component, uint32_t color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the pressed font color of a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
@@ -129,7 +266,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_pressed_font_color("button", "17013");
 | 
			
		||||
   * it.set_component_pressed_font_color("button", "RED");
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the pressed font color of the component `button` to a blue color.
 | 
			
		||||
@@ -137,6 +274,21 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   * Nextion HMI colors.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_pressed_font_color(const char *component, const char *color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the pressed font color of a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
   * @param color The color (as Color).
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_component_pressed_font_color("button", color);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * This will change the pressed font color of the component `button` to a blue color.
 | 
			
		||||
   * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to
 | 
			
		||||
   * Nextion HMI colors.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_pressed_font_color(const char *component, Color color) override;
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the coordinates of a component on screen.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
@@ -163,7 +315,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   *
 | 
			
		||||
   * Changes the font of the component named `textveiw`. Font IDs are set in the Nextion Editor.
 | 
			
		||||
   */
 | 
			
		||||
  void set_component_font(const char *component, uint8_t font_id);
 | 
			
		||||
  void set_component_font(const char *component, uint8_t font_id) override;
 | 
			
		||||
#ifdef USE_TIME
 | 
			
		||||
  /**
 | 
			
		||||
   * Send the current time to the nextion display.
 | 
			
		||||
@@ -195,7 +347,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   *
 | 
			
		||||
   * Hides the component named `button`.
 | 
			
		||||
   */
 | 
			
		||||
  void hide_component(const char *component);
 | 
			
		||||
  void hide_component(const char *component) override;
 | 
			
		||||
  /**
 | 
			
		||||
   * Show a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
@@ -207,7 +359,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   *
 | 
			
		||||
   * Shows the component named `button`.
 | 
			
		||||
   */
 | 
			
		||||
  void show_component(const char *component);
 | 
			
		||||
  void show_component(const char *component) override;
 | 
			
		||||
  /**
 | 
			
		||||
   * Enable touch for a component.
 | 
			
		||||
   * @param component The component name.
 | 
			
		||||
@@ -239,6 +391,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   * @param value The value to write.
 | 
			
		||||
   */
 | 
			
		||||
  void add_waveform_data(int component_id, uint8_t channel_number, uint8_t value);
 | 
			
		||||
  void open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value);
 | 
			
		||||
  /**
 | 
			
		||||
   * Display a picture at coordinates.
 | 
			
		||||
   * @param picture_id The picture id.
 | 
			
		||||
@@ -263,7 +416,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * fill_area(50, 50, 100, 100, "17013");
 | 
			
		||||
   * fill_area(50, 50, 100, 100, "RED");
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with
 | 
			
		||||
@@ -271,6 +424,24 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   * convert color codes to Nextion HMI colors
 | 
			
		||||
   */
 | 
			
		||||
  void fill_area(int x1, int y1, int width, int height, const char *color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Fill a rectangle with a color.
 | 
			
		||||
   * @param x1 The starting x coordinate.
 | 
			
		||||
   * @param y1 The starting y coordinate.
 | 
			
		||||
   * @param width The width to draw.
 | 
			
		||||
   * @param height The height to draw.
 | 
			
		||||
   * @param color The color to draw with (as Color).
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * fill_area(50, 50, 100, 100, color);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with
 | 
			
		||||
   * the color of blue. Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to
 | 
			
		||||
   * convert color codes to Nextion HMI colors
 | 
			
		||||
   */
 | 
			
		||||
  void fill_area(int x1, int y1, int width, int height, Color color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Draw a line on the screen.
 | 
			
		||||
   * @param x1 The starting x coordinate.
 | 
			
		||||
@@ -290,6 +461,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   * colors.
 | 
			
		||||
   */
 | 
			
		||||
  void line(int x1, int y1, int x2, int y2, const char *color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Draw a line on the screen.
 | 
			
		||||
   * @param x1 The starting x coordinate.
 | 
			
		||||
   * @param y1 The starting y coordinate.
 | 
			
		||||
   * @param x2 The ending x coordinate.
 | 
			
		||||
   * @param y2 The ending y coordinate.
 | 
			
		||||
   * @param color The color to draw with (as Color).
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.line(50, 50, 75, 75, "17013");
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * Makes a line that starts at x coordinate `50` and y coordinate `50` and ends at x coordinate `75` and y coordinate
 | 
			
		||||
   * `75` with the color of blue. Use this [color
 | 
			
		||||
   * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI
 | 
			
		||||
   * colors.
 | 
			
		||||
   */
 | 
			
		||||
  void line(int x1, int y1, int x2, int y2, Color color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Draw a rectangle outline.
 | 
			
		||||
   * @param x1 The starting x coordinate.
 | 
			
		||||
@@ -309,6 +499,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   * colors.
 | 
			
		||||
   */
 | 
			
		||||
  void rectangle(int x1, int y1, int width, int height, const char *color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Draw a rectangle outline.
 | 
			
		||||
   * @param x1 The starting x coordinate.
 | 
			
		||||
   * @param y1 The starting y coordinate.
 | 
			
		||||
   * @param width The width of the rectangle.
 | 
			
		||||
   * @param height The height of the rectangle.
 | 
			
		||||
   * @param color The color to draw with (as Color).
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.rectangle(25, 35, 40, 50, "17013");
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * Makes a outline of a rectangle that starts at x coordinate `25` and y coordinate `35` and has a width of `40` and a
 | 
			
		||||
   * length of `50` with color of blue. Use this [color
 | 
			
		||||
   * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI
 | 
			
		||||
   * colors.
 | 
			
		||||
   */
 | 
			
		||||
  void rectangle(int x1, int y1, int width, int height, Color color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Draw a circle outline
 | 
			
		||||
   * @param center_x The center x coordinate.
 | 
			
		||||
@@ -317,6 +526,14 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   * @param color The color to draw with (as a string).
 | 
			
		||||
   */
 | 
			
		||||
  void circle(int center_x, int center_y, int radius, const char *color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Draw a circle outline
 | 
			
		||||
   * @param center_x The center x coordinate.
 | 
			
		||||
   * @param center_y The center y coordinate.
 | 
			
		||||
   * @param radius The circle radius.
 | 
			
		||||
   * @param color The color to draw with (as Color).
 | 
			
		||||
   */
 | 
			
		||||
  void circle(int center_x, int center_y, int radius, Color color);
 | 
			
		||||
  /**
 | 
			
		||||
   * Draw a filled circled.
 | 
			
		||||
   * @param center_x The center x coordinate.
 | 
			
		||||
@@ -334,19 +551,36 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   * Nextion HMI colors.
 | 
			
		||||
   */
 | 
			
		||||
  void filled_circle(int center_x, int center_y, int radius, const char *color);
 | 
			
		||||
 | 
			
		||||
  /** Set the brightness of the backlight.
 | 
			
		||||
   *
 | 
			
		||||
   * @param brightness The brightness, from 0 to 100.
 | 
			
		||||
  /**
 | 
			
		||||
   * Draw a filled circled.
 | 
			
		||||
   * @param center_x The center x coordinate.
 | 
			
		||||
   * @param center_y The center y coordinate.
 | 
			
		||||
   * @param radius The circle radius.
 | 
			
		||||
   * @param color The color to draw with (as Color).
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_backlight_brightness(30);
 | 
			
		||||
   * it.filled_cricle(25, 25, 10, color);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * Makes a filled circle at the x cordinates `25` and y coordinate `25` with a radius of `10` with a color of blue.
 | 
			
		||||
   * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to
 | 
			
		||||
   * Nextion HMI colors.
 | 
			
		||||
   */
 | 
			
		||||
  void filled_circle(int center_x, int center_y, int radius, Color color);
 | 
			
		||||
 | 
			
		||||
  /** Set the brightness of the backlight.
 | 
			
		||||
   *
 | 
			
		||||
   * @param brightness The brightness percentage from 0 to 1.0.
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_backlight_brightness(.3);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * Changes the brightness of the display to 30%.
 | 
			
		||||
   */
 | 
			
		||||
  void set_backlight_brightness(uint8_t brightness);
 | 
			
		||||
  void set_backlight_brightness(float brightness);
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the touch sleep timeout of the display.
 | 
			
		||||
   * @param timeout Timeout in seconds.
 | 
			
		||||
@@ -360,10 +594,46 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   * `thup`.
 | 
			
		||||
   */
 | 
			
		||||
  void set_touch_sleep_timeout(uint16_t timeout);
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
 | 
			
		||||
   * @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
 | 
			
		||||
   * wakes up to current page.
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_wake_up_page(2);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * The display will wake up to page 2.
 | 
			
		||||
   */
 | 
			
		||||
  void set_wake_up_page(uint8_t page_id = 255);
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets if Nextion should auto-wake from sleep when touch press occurs.
 | 
			
		||||
   * @param auto_wake True or false. When auto_wake is true and Nextion is in sleep mode,
 | 
			
		||||
   * the first touch will only trigger the auto wake mode and not trigger a Touch Event.
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * it.set_auto_wake_on_touch(true);
 | 
			
		||||
   * ```
 | 
			
		||||
   *
 | 
			
		||||
   * The display will wake up by touch.
 | 
			
		||||
   */
 | 
			
		||||
  void set_auto_wake_on_touch(bool auto_wake);
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets Nextion mode between sleep and awake
 | 
			
		||||
   * @param True or false. Sleep=true to enter sleep mode or sleep=false to exit sleep mode.
 | 
			
		||||
   */
 | 
			
		||||
  void sleep(bool sleep);
 | 
			
		||||
 | 
			
		||||
  // ========== INTERNAL METHODS ==========
 | 
			
		||||
  // (In most use cases you won't need these)
 | 
			
		||||
  void register_touch_component(NextionTouchComponent *obj) { this->touch_.push_back(obj); }
 | 
			
		||||
  void register_touch_component(NextionComponentBase *obj) { this->touch_.push_back(obj); }
 | 
			
		||||
  void register_switch_component(NextionComponentBase *obj) { this->switchtype_.push_back(obj); }
 | 
			
		||||
  void register_binarysensor_component(NextionComponentBase *obj) { this->binarysensortype_.push_back(obj); }
 | 
			
		||||
  void register_sensor_component(NextionComponentBase *obj) { this->sensortype_.push_back(obj); }
 | 
			
		||||
  void register_textsensor_component(NextionComponentBase *obj) { this->textsensortype_.push_back(obj); }
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void set_brightness(float brightness) { this->brightness_ = brightness; }
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
@@ -371,11 +641,9 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void set_writer(const nextion_writer_t &writer);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Manually send a raw command to the display and don't wait for an acknowledgement packet.
 | 
			
		||||
   * @param command The command to write, for example "vis b0,0".
 | 
			
		||||
   */
 | 
			
		||||
  void send_command_no_ack(const char *command);
 | 
			
		||||
  // This function has been deprecated
 | 
			
		||||
  void set_wait_for_ack(bool wait_for_ack);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Manually send a raw formatted command to the display.
 | 
			
		||||
   * @param format The printf-style command format, like "vis %s,0"
 | 
			
		||||
@@ -384,28 +652,199 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
   */
 | 
			
		||||
  bool send_command_printf(const char *format, ...) __attribute__((format(printf, 2, 3)));
 | 
			
		||||
 | 
			
		||||
  void set_wait_for_ack(bool wait_for_ack);
 | 
			
		||||
#ifdef USE_TFT_UPLOAD
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the tft file URL. https seems problamtic with arduino..
 | 
			
		||||
   */
 | 
			
		||||
  void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; }
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Upload the tft file and softreset the Nextion
 | 
			
		||||
   */
 | 
			
		||||
  void upload_tft();
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Softreset the Nextion
 | 
			
		||||
   */
 | 
			
		||||
  void soft_reset();
 | 
			
		||||
 | 
			
		||||
  /** Add a callback to be notified of sleep state changes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param callback The void() callback.
 | 
			
		||||
   */
 | 
			
		||||
  void add_sleep_state_callback(std::function<void()> &&callback);
 | 
			
		||||
 | 
			
		||||
  /** Add a callback to be notified of wake state changes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param callback The void() callback.
 | 
			
		||||
   */
 | 
			
		||||
  void add_wake_state_callback(std::function<void()> &&callback);
 | 
			
		||||
 | 
			
		||||
  /** Add a callback to be notified when the nextion completes its initialize setup.
 | 
			
		||||
   *
 | 
			
		||||
   * @param callback The void() callback.
 | 
			
		||||
   */
 | 
			
		||||
  void add_setup_state_callback(std::function<void()> &&callback);
 | 
			
		||||
 | 
			
		||||
  void update_all_components();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Set the nextion sensor state object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param[in] queue_type
 | 
			
		||||
   * Index of NextionQueueType.
 | 
			
		||||
   *
 | 
			
		||||
   * @param[in] name
 | 
			
		||||
   * Component/variable name.
 | 
			
		||||
   *
 | 
			
		||||
   * @param[in] state
 | 
			
		||||
   * State to set.
 | 
			
		||||
   */
 | 
			
		||||
  void set_nextion_sensor_state(int queue_type, const std::string &name, float state);
 | 
			
		||||
  void set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state);
 | 
			
		||||
  void set_nextion_text_state(const std::string &name, const std::string &state);
 | 
			
		||||
 | 
			
		||||
  void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) override;
 | 
			
		||||
  void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send,
 | 
			
		||||
                                       int state_value) override;
 | 
			
		||||
 | 
			
		||||
  void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) override;
 | 
			
		||||
  void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send,
 | 
			
		||||
                                       const std::string &state_value) override;
 | 
			
		||||
 | 
			
		||||
  void add_to_get_queue(NextionComponentBase *component) override;
 | 
			
		||||
 | 
			
		||||
  void add_addt_command_to_queue(NextionComponentBase *component) override;
 | 
			
		||||
 | 
			
		||||
  void update_components_by_prefix(const std::string &prefix);
 | 
			
		||||
 | 
			
		||||
  void set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) {
 | 
			
		||||
    this->touch_sleep_timeout_ = touch_sleep_timeout;
 | 
			
		||||
  }
 | 
			
		||||
  void set_wake_up_page_internal(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; }
 | 
			
		||||
  void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool ack_();
 | 
			
		||||
  bool read_until_ack_();
 | 
			
		||||
  std::deque<NextionQueue *> nextion_queue_;
 | 
			
		||||
  uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag);
 | 
			
		||||
  void all_components_send_state_(bool force_update = false);
 | 
			
		||||
  uint64_t comok_sent_ = 0;
 | 
			
		||||
  bool remove_from_q_(bool report_empty = true);
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief
 | 
			
		||||
   * Sends commands ignoring of the Nextion has been setup.
 | 
			
		||||
   */
 | 
			
		||||
  bool ignore_is_setup_ = false;
 | 
			
		||||
  bool nextion_reports_is_setup_ = false;
 | 
			
		||||
  uint8_t nextion_event_;
 | 
			
		||||
 | 
			
		||||
  void process_nextion_commands_();
 | 
			
		||||
  void process_serial_();
 | 
			
		||||
  bool is_updating_ = false;
 | 
			
		||||
  uint32_t touch_sleep_timeout_ = 0;
 | 
			
		||||
  int wake_up_page_ = -1;
 | 
			
		||||
  bool auto_wake_on_touch_ = true;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Manually send a raw command to the display and don't wait for an acknowledgement packet.
 | 
			
		||||
   * @param command The command to write, for example "vis b0,0".
 | 
			
		||||
   */
 | 
			
		||||
  bool send_command_(const std::string &command);
 | 
			
		||||
  void add_no_result_to_queue_(const std::string &variable_name);
 | 
			
		||||
  bool add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format, ...)
 | 
			
		||||
      __attribute__((format(printf, 3, 4)));
 | 
			
		||||
  void add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command);
 | 
			
		||||
 | 
			
		||||
  bool add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...)
 | 
			
		||||
      __attribute__((format(printf, 3, 4)));
 | 
			
		||||
 | 
			
		||||
  void add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
 | 
			
		||||
                                                 const std::string &variable_name_to_send, int state_value,
 | 
			
		||||
                                                 bool is_sleep_safe = false);
 | 
			
		||||
 | 
			
		||||
  void add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
 | 
			
		||||
                                                 const std::string &variable_name_to_send,
 | 
			
		||||
                                                 const std::string &state_value, bool is_sleep_safe = false);
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TFT_UPLOAD
 | 
			
		||||
#if defined(USE_ETHERNET) || defined(USE_WIFI)
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP8266
 | 
			
		||||
  WiFiClient *wifi_client_{nullptr};
 | 
			
		||||
  BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr};
 | 
			
		||||
  WiFiClient *get_wifi_client_();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * will request chunk_size chunks from the web server
 | 
			
		||||
   * and send each to the nextion
 | 
			
		||||
   * @param int contentLength Total size of the file
 | 
			
		||||
   * @param uint32_t chunk_size
 | 
			
		||||
   * @return true if success, false for failure.
 | 
			
		||||
   */
 | 
			
		||||
  int content_length_ = 0;
 | 
			
		||||
  int tft_size_ = 0;
 | 
			
		||||
  int upload_by_chunks_(HTTPClient *http, int range_start);
 | 
			
		||||
 | 
			
		||||
  bool upload_with_range_(uint32_t range_start, uint32_t range_end);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * start update tft file to nextion.
 | 
			
		||||
   *
 | 
			
		||||
   * @param const uint8_t *file_buf
 | 
			
		||||
   * @param size_t buf_size
 | 
			
		||||
   * @return true if success, false for failure.
 | 
			
		||||
   */
 | 
			
		||||
  bool upload_from_buffer_(const uint8_t *file_buf, size_t buf_size);
 | 
			
		||||
  void upload_end_();
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  bool get_is_connected_() { return this->is_connected_; }
 | 
			
		||||
 | 
			
		||||
  bool check_connect_();
 | 
			
		||||
 | 
			
		||||
  std::vector<NextionComponentBase *> touch_;
 | 
			
		||||
  std::vector<NextionComponentBase *> switchtype_;
 | 
			
		||||
  std::vector<NextionComponentBase *> sensortype_;
 | 
			
		||||
  std::vector<NextionComponentBase *> textsensortype_;
 | 
			
		||||
  std::vector<NextionComponentBase *> binarysensortype_;
 | 
			
		||||
  CallbackManager<void()> setup_callback_{};
 | 
			
		||||
  CallbackManager<void()> sleep_callback_{};
 | 
			
		||||
  CallbackManager<void()> wake_callback_{};
 | 
			
		||||
 | 
			
		||||
  std::vector<NextionTouchComponent *> touch_;
 | 
			
		||||
  optional<nextion_writer_t> writer_;
 | 
			
		||||
  bool wait_for_ack_{true};
 | 
			
		||||
  float brightness_{1.0};
 | 
			
		||||
 | 
			
		||||
  std::string device_model_;
 | 
			
		||||
  std::string firmware_version_;
 | 
			
		||||
  std::string serial_number_;
 | 
			
		||||
  std::string flash_size_;
 | 
			
		||||
 | 
			
		||||
  void remove_front_no_sensors_();
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TFT_UPLOAD
 | 
			
		||||
  std::string tft_url_;
 | 
			
		||||
  uint8_t *transfer_buffer_{nullptr};
 | 
			
		||||
  size_t transfer_buffer_size_;
 | 
			
		||||
  bool upload_first_chunk_sent_ = false;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef NEXTION_PROTOCOL_LOG
 | 
			
		||||
  void print_queue_members_();
 | 
			
		||||
#endif
 | 
			
		||||
  void reset_(bool reset_nextion = true);
 | 
			
		||||
 | 
			
		||||
  std::string command_data_;
 | 
			
		||||
  bool is_connected_ = false;
 | 
			
		||||
  uint32_t startup_override_ms_ = 8000;
 | 
			
		||||
  uint32_t max_q_age_ms_ = 8000;
 | 
			
		||||
  uint32_t started_ms_ = 0;
 | 
			
		||||
  bool sent_setup_commands_ = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class NextionTouchComponent : public binary_sensor::BinarySensorInitiallyOff {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_page_id(uint8_t page_id) { page_id_ = page_id; }
 | 
			
		||||
  void set_component_id(uint8_t component_id) { component_id_ = component_id; }
 | 
			
		||||
  void process(uint8_t page_id, uint8_t component_id, bool on);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t page_id_;
 | 
			
		||||
  uint8_t component_id_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								esphome/components/nextion/nextion_base.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								esphome/components/nextion/nextion_base.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/color.h"
 | 
			
		||||
#include "nextion_component_base.h"
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
 | 
			
		||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
 | 
			
		||||
#define NEXTION_PROTOCOL_LOG
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef NEXTION_PROTOCOL_LOG
 | 
			
		||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
 | 
			
		||||
#define ESP_LOGN(tag, ...) ESP_LOGVV(tag, __VA_ARGS__)
 | 
			
		||||
#else
 | 
			
		||||
#define ESP_LOGN(tag, ...) ESP_LOGD(tag, __VA_ARGS__)
 | 
			
		||||
#endif
 | 
			
		||||
#else
 | 
			
		||||
#define ESP_LOGN(tag, ...) \
 | 
			
		||||
  {}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
class NextionBase;
 | 
			
		||||
 | 
			
		||||
class NextionBase {
 | 
			
		||||
 public:
 | 
			
		||||
  virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) = 0;
 | 
			
		||||
  virtual void add_no_result_to_queue_with_set(const std::string &variable_name,
 | 
			
		||||
                                               const std::string &variable_name_to_send, int state_value) = 0;
 | 
			
		||||
 | 
			
		||||
  virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) = 0;
 | 
			
		||||
  virtual void add_no_result_to_queue_with_set(const std::string &variable_name,
 | 
			
		||||
                                               const std::string &variable_name_to_send,
 | 
			
		||||
                                               const std::string &state_value) = 0;
 | 
			
		||||
 | 
			
		||||
  virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0;
 | 
			
		||||
 | 
			
		||||
  virtual void add_to_get_queue(NextionComponentBase *component) = 0;
 | 
			
		||||
 | 
			
		||||
  virtual void set_component_background_color(const char *component, Color color) = 0;
 | 
			
		||||
  virtual void set_component_pressed_background_color(const char *component, Color color) = 0;
 | 
			
		||||
  virtual void set_component_font_color(const char *component, Color color) = 0;
 | 
			
		||||
  virtual void set_component_pressed_font_color(const char *component, Color color) = 0;
 | 
			
		||||
  virtual void set_component_font(const char *component, uint8_t font_id) = 0;
 | 
			
		||||
 | 
			
		||||
  virtual void show_component(const char *component) = 0;
 | 
			
		||||
  virtual void hide_component(const char *component) = 0;
 | 
			
		||||
 | 
			
		||||
  bool is_sleeping() { return this->is_sleeping_; }
 | 
			
		||||
  bool is_setup() { return this->is_setup_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool is_setup_ = false;
 | 
			
		||||
  bool is_sleeping_ = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										234
									
								
								esphome/components/nextion/nextion_commands.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								esphome/components/nextion/nextion_commands.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,234 @@
 | 
			
		||||
#include "nextion.h"
 | 
			
		||||
#include "esphome/core/util.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
static const char *const TAG = "nextion";
 | 
			
		||||
 | 
			
		||||
// Sleep safe commands
 | 
			
		||||
void Nextion::soft_reset() { this->send_command_("rest"); }
 | 
			
		||||
 | 
			
		||||
void Nextion::set_wake_up_page(uint8_t page_id) {
 | 
			
		||||
  if (page_id > 255) {
 | 
			
		||||
    ESP_LOGD(TAG, "Wake up page of bounds, range 0-255");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_touch_sleep_timeout(uint16_t timeout) {
 | 
			
		||||
  if (timeout < 3 || timeout > 65535) {
 | 
			
		||||
    ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", timeout, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::sleep(bool sleep) {
 | 
			
		||||
  if (sleep) {  // Set sleep
 | 
			
		||||
    this->is_sleeping_ = true;
 | 
			
		||||
    this->add_no_result_to_queue_with_set_internal_("sleep", "sleep", 1, true);
 | 
			
		||||
  } else {  // Turn off sleep. Wait for a sleep_wake return before setting sleep off
 | 
			
		||||
    this->add_no_result_to_queue_with_set_internal_("sleep_wake", "sleep", 0, true);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// End sleep safe commands
 | 
			
		||||
 | 
			
		||||
// Set Colors
 | 
			
		||||
void Nextion::set_component_background_color(const char *component, uint32_t color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_background_color(const char *component, const char *color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%s", component, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_background_color(const char *component, Color color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component,
 | 
			
		||||
                                            display::ColorUtil::color_to_565(color));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_pressed_background_color(const char *component, uint32_t color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_pressed_background_color(const char *component, const char *color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%s", component, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_pressed_background_color(const char *component, Color color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component,
 | 
			
		||||
                                            display::ColorUtil::color_to_565(color));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_pic(const char *component, uint8_t pic_id) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.pic=%d", component, pic_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_picc(const char *component, uint8_t pic_id) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.picc=%d", component, pic_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_font_color(const char *component, uint32_t color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_font_color(const char *component, const char *color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%s", component, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_font_color(const char *component, Color color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component,
 | 
			
		||||
                                            display::ColorUtil::color_to_565(color));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_pressed_font_color(const char *component, uint32_t color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_pressed_font_color(const char *component, const char *color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", " %s.pco2=%s", component, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_pressed_font_color(const char *component, Color color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component,
 | 
			
		||||
                                            display::ColorUtil::color_to_565(color));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_text_printf(const char *component, const char *format, ...) {
 | 
			
		||||
  va_list arg;
 | 
			
		||||
  va_start(arg, format);
 | 
			
		||||
  char buffer[256];
 | 
			
		||||
  int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
 | 
			
		||||
  va_end(arg);
 | 
			
		||||
  if (ret > 0)
 | 
			
		||||
    this->set_component_text(component, buffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// General Nextion
 | 
			
		||||
void Nextion::goto_page(const char *page) { this->add_no_result_to_queue_with_printf_("goto_page", "page %s", page); }
 | 
			
		||||
 | 
			
		||||
void Nextion::set_backlight_brightness(float brightness) {
 | 
			
		||||
  if (brightness < 0 || brightness > 1.0) {
 | 
			
		||||
    ESP_LOGD(TAG, "Brightness out of bounds, percentage range 0-1.0");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->add_no_result_to_queue_with_set("backlight_brightness", "dim", static_cast<int>(brightness * 100));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_auto_wake_on_touch(bool auto_wake) {
 | 
			
		||||
  this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// General Component
 | 
			
		||||
void Nextion::set_component_font(const char *component, uint8_t font_id) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_font", "%s.font=%d", component, font_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::hide_component(const char *component) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("hide_component", "vis %s,0", component);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::show_component(const char *component) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("show_component", "vis %s,1", component);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::enable_component_touch(const char *component) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("enable_component_touch", "tsw %s,1", component);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::disable_component_touch(const char *component) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("disable_component_touch", "tsw %s,0", component);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_picture(const char *component, const char *picture) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.val=%s", component, picture);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_text(const char *component, const char *text) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_text", "%s.txt=\"%s\"", component, text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_value(const char *component, int value) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_value", "%s.val=%d", component, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("add_waveform_data", "add %d,%u,%u", component_id, channel_number, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("open_waveform_channel", "addt %d,%u,%u", component_id, channel_number,
 | 
			
		||||
                                            value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::set_component_coordinates(const char *component, int x, int y) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_coordinates command 1", "%s.xcen=%d", component, x);
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("set_component_coordinates command 2", "%s.ycen=%d", component, y);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Drawing
 | 
			
		||||
void Nextion::display_picture(int picture_id, int x_start, int y_start) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("display_picture", "pic %d %d %d", x_start, y_start, picture_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%s", x1, y1, width, height, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::fill_area(int x1, int y1, int width, int height, Color color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%d", x1, y1, width, height,
 | 
			
		||||
                                            display::ColorUtil::color_to_565(color));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::line(int x1, int y1, int x2, int y2, const char *color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%s", x1, y1, x2, y2, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::line(int x1, int y1, int x2, int y2, Color color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%d", x1, y1, x2, y2,
 | 
			
		||||
                                            display::ColorUtil::color_to_565(color));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::rectangle(int x1, int y1, int width, int height, Color color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%d", x1, y1, x1 + width, y1 + height,
 | 
			
		||||
                                            display::ColorUtil::color_to_565(color));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::circle(int center_x, int center_y, int radius, const char *color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%s", center_x, center_y, radius, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::circle(int center_x, int center_y, int radius, Color color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%d", center_x, center_y, radius,
 | 
			
		||||
                                            display::ColorUtil::color_to_565(color));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%s", center_x, center_y, radius, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::filled_circle(int center_x, int center_y, int radius, Color color) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%d", center_x, center_y, radius,
 | 
			
		||||
                                            display::ColorUtil::color_to_565(color));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TIME
 | 
			
		||||
void Nextion::set_nextion_rtc_time(time::ESPTime time) {
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("rtc0", "rtc0=%u", time.year);
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("rtc1", "rtc1=%u", time.month);
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("rtc2", "rtc2=%u", time.day_of_month);
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("rtc3", "rtc3=%u", time.hour);
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("rtc4", "rtc4=%u", time.minute);
 | 
			
		||||
  this->add_no_result_to_queue_with_printf_("rtc5", "rtc5=%u", time.second);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										116
									
								
								esphome/components/nextion/nextion_component.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								esphome/components/nextion/nextion_component.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
#include "nextion_component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
 | 
			
		||||
void NextionComponent::set_background_color(Color bco) {
 | 
			
		||||
  if (this->variable_name_ == this->variable_name_to_send_) {
 | 
			
		||||
    return;  // This is a variable. no need to set color
 | 
			
		||||
  }
 | 
			
		||||
  this->bco_ = bco;
 | 
			
		||||
  this->bco_needs_update_ = true;
 | 
			
		||||
  this->bco_is_set_ = true;
 | 
			
		||||
  this->update_component_settings();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionComponent::set_background_pressed_color(Color bco2) {
 | 
			
		||||
  if (this->variable_name_ == this->variable_name_to_send_) {
 | 
			
		||||
    return;  // This is a variable. no need to set color
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->bco2_ = bco2;
 | 
			
		||||
  this->bco2_needs_update_ = true;
 | 
			
		||||
  this->bco2_is_set_ = true;
 | 
			
		||||
  this->update_component_settings();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionComponent::set_foreground_color(Color pco) {
 | 
			
		||||
  if (this->variable_name_ == this->variable_name_to_send_) {
 | 
			
		||||
    return;  // This is a variable. no need to set color
 | 
			
		||||
  }
 | 
			
		||||
  this->pco_ = pco;
 | 
			
		||||
  this->pco_needs_update_ = true;
 | 
			
		||||
  this->pco_is_set_ = true;
 | 
			
		||||
  this->update_component_settings();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionComponent::set_foreground_pressed_color(Color pco2) {
 | 
			
		||||
  if (this->variable_name_ == this->variable_name_to_send_) {
 | 
			
		||||
    return;  // This is a variable. no need to set color
 | 
			
		||||
  }
 | 
			
		||||
  this->pco2_ = pco2;
 | 
			
		||||
  this->pco2_needs_update_ = true;
 | 
			
		||||
  this->pco2_is_set_ = true;
 | 
			
		||||
  this->update_component_settings();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionComponent::set_font_id(uint8_t font_id) {
 | 
			
		||||
  if (this->variable_name_ == this->variable_name_to_send_) {
 | 
			
		||||
    return;  // This is a variable. no need to set color
 | 
			
		||||
  }
 | 
			
		||||
  this->font_id_ = font_id;
 | 
			
		||||
  this->font_id_needs_update_ = true;
 | 
			
		||||
  this->font_id_is_set_ = true;
 | 
			
		||||
  this->update_component_settings();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionComponent::set_visible(bool visible) {
 | 
			
		||||
  if (this->variable_name_ == this->variable_name_to_send_) {
 | 
			
		||||
    return;  // This is a variable. no need to set color
 | 
			
		||||
  }
 | 
			
		||||
  this->visible_ = visible;
 | 
			
		||||
  this->visible_needs_update_ = true;
 | 
			
		||||
  this->visible_is_set_ = true;
 | 
			
		||||
  this->update_component_settings();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionComponent::update_component_settings(bool force_update) {
 | 
			
		||||
  if (this->nextion_->is_sleeping() || !this->nextion_->is_setup() || !this->visible_is_set_ ||
 | 
			
		||||
      (!this->visible_needs_update_ && !this->visible_)) {
 | 
			
		||||
    this->needs_to_send_update_ = true;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->visible_needs_update_ || (force_update && this->visible_is_set_)) {
 | 
			
		||||
    std::string name_to_send = this->variable_name_;
 | 
			
		||||
 | 
			
		||||
    size_t pos = name_to_send.find_last_of('.');
 | 
			
		||||
    if (pos != std::string::npos) {
 | 
			
		||||
      name_to_send = name_to_send.substr(pos + 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->visible_needs_update_ = false;
 | 
			
		||||
 | 
			
		||||
    if (this->visible_) {
 | 
			
		||||
      this->nextion_->show_component(name_to_send.c_str());
 | 
			
		||||
      this->send_state_to_nextion();
 | 
			
		||||
    } else {
 | 
			
		||||
      this->nextion_->hide_component(name_to_send.c_str());
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->bco_needs_update_ || (force_update && this->bco2_is_set_)) {
 | 
			
		||||
    this->nextion_->set_component_background_color(this->variable_name_.c_str(), this->bco_);
 | 
			
		||||
    this->bco_needs_update_ = false;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->bco2_needs_update_ || (force_update && this->bco2_is_set_)) {
 | 
			
		||||
    this->nextion_->set_component_pressed_background_color(this->variable_name_.c_str(), this->bco2_);
 | 
			
		||||
    this->bco2_needs_update_ = false;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->pco_needs_update_ || (force_update && this->pco_is_set_)) {
 | 
			
		||||
    this->nextion_->set_component_font_color(this->variable_name_.c_str(), this->pco_);
 | 
			
		||||
    this->pco_needs_update_ = false;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->pco2_needs_update_ || (force_update && this->pco2_is_set_)) {
 | 
			
		||||
    this->nextion_->set_component_pressed_font_color(this->variable_name_.c_str(), this->pco2_);
 | 
			
		||||
    this->pco2_needs_update_ = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->font_id_needs_update_ || (force_update && this->font_id_is_set_)) {
 | 
			
		||||
    this->nextion_->set_component_font(this->variable_name_.c_str(), this->font_id_);
 | 
			
		||||
    this->font_id_needs_update_ = false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										49
									
								
								esphome/components/nextion/nextion_component.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								esphome/components/nextion/nextion_component.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/color.h"
 | 
			
		||||
#include "nextion_base.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
class NextionComponent;
 | 
			
		||||
 | 
			
		||||
class NextionComponent : public NextionComponentBase {
 | 
			
		||||
 public:
 | 
			
		||||
  void update_component_settings() override { this->update_component_settings(false); };
 | 
			
		||||
 | 
			
		||||
  void update_component_settings(bool force_update) override;
 | 
			
		||||
 | 
			
		||||
  void set_background_color(Color bco);
 | 
			
		||||
  void set_background_pressed_color(Color bco2);
 | 
			
		||||
  void set_foreground_color(Color pco);
 | 
			
		||||
  void set_foreground_pressed_color(Color pco2);
 | 
			
		||||
  void set_font_id(uint8_t font_id);
 | 
			
		||||
  void set_visible(bool visible);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  NextionBase *nextion_;
 | 
			
		||||
 | 
			
		||||
  bool bco_needs_update_ = false;
 | 
			
		||||
  bool bco_is_set_ = false;
 | 
			
		||||
  Color bco_;
 | 
			
		||||
  bool bco2_needs_update_ = false;
 | 
			
		||||
  bool bco2_is_set_ = false;
 | 
			
		||||
  Color bco2_;
 | 
			
		||||
  bool pco_needs_update_ = false;
 | 
			
		||||
  bool pco_is_set_ = false;
 | 
			
		||||
  Color pco_;
 | 
			
		||||
  bool pco2_needs_update_ = false;
 | 
			
		||||
  bool pco2_is_set_ = false;
 | 
			
		||||
  Color pco2_;
 | 
			
		||||
  uint8_t font_id_ = 0;
 | 
			
		||||
  bool font_id_needs_update_ = false;
 | 
			
		||||
  bool font_id_is_set_ = false;
 | 
			
		||||
 | 
			
		||||
  bool visible_ = true;
 | 
			
		||||
  bool visible_needs_update_ = false;
 | 
			
		||||
  bool visible_is_set_ = false;
 | 
			
		||||
 | 
			
		||||
  // void send_state_to_nextion() = 0;
 | 
			
		||||
};
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										95
									
								
								esphome/components/nextion/nextion_component_base.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								esphome/components/nextion/nextion_component_base.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
 | 
			
		||||
enum NextionQueueType {
 | 
			
		||||
  NO_RESULT = 0,
 | 
			
		||||
  SENSOR = 1,
 | 
			
		||||
  BINARY_SENSOR = 2,
 | 
			
		||||
  SWITCH = 3,
 | 
			
		||||
  TEXT_SENSOR = 4,
 | 
			
		||||
  WAVEFORM_SENSOR = 5,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const char *const NEXTION_QUEUE_TYPE_STRINGS[] = {"NO_RESULT", "SENSOR",      "BINARY_SENSOR",
 | 
			
		||||
                                                         "SWITCH",    "TEXT_SENSOR", "WAVEFORM_SENSOR"};
 | 
			
		||||
 | 
			
		||||
class NextionComponentBase;
 | 
			
		||||
 | 
			
		||||
class NextionQueue {
 | 
			
		||||
 public:
 | 
			
		||||
  virtual ~NextionQueue() = default;
 | 
			
		||||
  NextionComponentBase *component;
 | 
			
		||||
  uint32_t queue_time = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class NextionComponentBase {
 | 
			
		||||
 public:
 | 
			
		||||
  virtual ~NextionComponentBase() = default;
 | 
			
		||||
 | 
			
		||||
  void set_variable_name(const std::string &variable_name, const std::string &variable_name_to_send = "") {
 | 
			
		||||
    variable_name_ = variable_name;
 | 
			
		||||
    if (variable_name_to_send.empty()) {
 | 
			
		||||
      variable_name_to_send_ = variable_name;
 | 
			
		||||
    } else {
 | 
			
		||||
      variable_name_to_send_ = variable_name_to_send;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void update_component_settings(){};
 | 
			
		||||
  virtual void update_component_settings(bool force_update){};
 | 
			
		||||
 | 
			
		||||
  virtual void update_component(){};
 | 
			
		||||
  virtual void process_sensor(const std::string &variable_name, int state){};
 | 
			
		||||
  virtual void process_touch(uint8_t page_id, uint8_t component_id, bool on){};
 | 
			
		||||
  virtual void process_text(const std::string &variable_name, const std::string &text_value){};
 | 
			
		||||
  virtual void process_bool(const std::string &variable_name, bool on){};
 | 
			
		||||
 | 
			
		||||
  virtual void set_state(float state){};
 | 
			
		||||
  virtual void set_state(float state, bool publish){};
 | 
			
		||||
  virtual void set_state(float state, bool publish, bool send_to_nextion){};
 | 
			
		||||
 | 
			
		||||
  virtual void set_state(bool state){};
 | 
			
		||||
  virtual void set_state(bool state, bool publish){};
 | 
			
		||||
  virtual void set_state(bool state, bool publish, bool send_to_nextion){};
 | 
			
		||||
 | 
			
		||||
  virtual void set_state(const std::string &state) {}
 | 
			
		||||
  virtual void set_state(const std::string &state, bool publish) {}
 | 
			
		||||
  virtual void set_state(const std::string &state, bool publish, bool send_to_nextion){};
 | 
			
		||||
 | 
			
		||||
  uint8_t get_component_id() { return this->component_id_; }
 | 
			
		||||
  void set_component_id(uint8_t component_id) { component_id_ = component_id; }
 | 
			
		||||
 | 
			
		||||
  uint8_t get_wave_channel_id() { return this->wave_chan_id_; }
 | 
			
		||||
  void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; }
 | 
			
		||||
 | 
			
		||||
  std::vector<uint8_t> get_wave_buffer() { return this->wave_buffer_; }
 | 
			
		||||
  size_t get_wave_buffer_size() { return this->wave_buffer_.size(); }
 | 
			
		||||
 | 
			
		||||
  std::string get_variable_name() { return this->variable_name_; }
 | 
			
		||||
  std::string get_variable_name_to_send() { return this->variable_name_to_send_; }
 | 
			
		||||
  virtual NextionQueueType get_queue_type() { return NextionQueueType::NO_RESULT; }
 | 
			
		||||
  virtual std::string get_queue_type_string() { return NEXTION_QUEUE_TYPE_STRINGS[this->get_queue_type()]; }
 | 
			
		||||
  virtual void set_state_from_int(int state_value, bool publish, bool send_to_nextion){};
 | 
			
		||||
  virtual void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion){};
 | 
			
		||||
  virtual void send_state_to_nextion(){};
 | 
			
		||||
  bool get_needs_to_send_update() { return this->needs_to_send_update_; }
 | 
			
		||||
  uint8_t get_wave_chan_id() { return this->wave_chan_id_; }
 | 
			
		||||
  void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::string variable_name_;
 | 
			
		||||
  std::string variable_name_to_send_;
 | 
			
		||||
 | 
			
		||||
  uint8_t component_id_ = 0;
 | 
			
		||||
  uint8_t wave_chan_id_ = UINT8_MAX;
 | 
			
		||||
  std::vector<uint8_t> wave_buffer_;
 | 
			
		||||
  int wave_max_length_ = 255;
 | 
			
		||||
 | 
			
		||||
  bool needs_to_send_update_;
 | 
			
		||||
};
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										343
									
								
								esphome/components/nextion/nextion_upload.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										343
									
								
								esphome/components/nextion/nextion_upload.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,343 @@
 | 
			
		||||
 | 
			
		||||
#include "nextion.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/util.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
static const char *const TAG = "nextion_upload";
 | 
			
		||||
 | 
			
		||||
#if defined(USE_TFT_UPLOAD) && (defined(USE_ETHERNET) || defined(USE_WIFI))
 | 
			
		||||
 | 
			
		||||
// Followed guide
 | 
			
		||||
// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
 | 
			
		||||
 | 
			
		||||
int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) {
 | 
			
		||||
  int range_end = 0;
 | 
			
		||||
 | 
			
		||||
  if (range_start == 0 && this->transfer_buffer_size_ > 16384) {  // Start small at the first run in case of a big skip
 | 
			
		||||
    range_end = 16384 - 1;
 | 
			
		||||
  } else {
 | 
			
		||||
    range_end = range_start + this->transfer_buffer_size_ - 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (range_end > this->tft_size_)
 | 
			
		||||
    range_end = this->tft_size_;
 | 
			
		||||
 | 
			
		||||
  bool begin_status = false;
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
  begin_status = http->begin(this->tft_url_.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP8266
 | 
			
		||||
#ifndef CLANG_TIDY
 | 
			
		||||
  http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
 | 
			
		||||
  http->setRedirectLimit(3);
 | 
			
		||||
  begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  char range_header[64];
 | 
			
		||||
  sprintf(range_header, "bytes=%d-%d", range_start, range_end);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Requesting range: %s", range_header);
 | 
			
		||||
 | 
			
		||||
  int tries = 1;
 | 
			
		||||
  int code = 0;
 | 
			
		||||
  while (tries <= 5) {
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
    begin_status = http->begin(this->tft_url_.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
#ifndef CLANG_TIDY
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP8266
 | 
			
		||||
    begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    ++tries;
 | 
			
		||||
    if (!begin_status) {
 | 
			
		||||
      ESP_LOGD(TAG, "upload_by_chunks_: connection failed");
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    http->addHeader("Range", range_header);
 | 
			
		||||
 | 
			
		||||
    code = http->GET();
 | 
			
		||||
    if (code == 200 || code == 206) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retries(%d/5)", this->tft_url_.c_str(),
 | 
			
		||||
             HTTPClient::errorToString(code).c_str(), tries);
 | 
			
		||||
    http->end();
 | 
			
		||||
    App.feed_wdt();
 | 
			
		||||
    delay(500);  // NOLINT
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (tries > 5) {
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::string recv_string;
 | 
			
		||||
  size_t size = 0;
 | 
			
		||||
  int sent = 0;
 | 
			
		||||
  int range = range_end - range_start;
 | 
			
		||||
 | 
			
		||||
  while (sent < range) {
 | 
			
		||||
    size = http->getStreamPtr()->available();
 | 
			
		||||
    if (!size) {
 | 
			
		||||
      App.feed_wdt();
 | 
			
		||||
      delay(0);
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    int c = http->getStreamPtr()->readBytes(
 | 
			
		||||
        &this->transfer_buffer_[sent], ((size > this->transfer_buffer_size_) ? this->transfer_buffer_size_ : size));
 | 
			
		||||
    sent += c;
 | 
			
		||||
  }
 | 
			
		||||
  http->end();
 | 
			
		||||
  ESP_LOGN(TAG, "this->content_length_ %d sent %d", this->content_length_, sent);
 | 
			
		||||
  for (uint32_t i = 0; i < range; i += 4096) {
 | 
			
		||||
    this->write_array(&this->transfer_buffer_[i], 4096);
 | 
			
		||||
    this->content_length_ -= 4096;
 | 
			
		||||
    ESP_LOGN(TAG, "this->content_length_ %d range %d range_end %d range_start %d", this->content_length_, range,
 | 
			
		||||
             range_end, range_start);
 | 
			
		||||
 | 
			
		||||
    if (!this->upload_first_chunk_sent_) {
 | 
			
		||||
      this->upload_first_chunk_sent_ = true;
 | 
			
		||||
      delay(500);  // NOLINT
 | 
			
		||||
      App.feed_wdt();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->recv_ret_string_(recv_string, 2048, true);
 | 
			
		||||
    if (recv_string[0] == 0x08) {
 | 
			
		||||
      uint32_t result = 0;
 | 
			
		||||
      for (int i = 0; i < 4; ++i) {
 | 
			
		||||
        result += static_cast<uint8_t>(recv_string[i + 1]) << (8 * i);
 | 
			
		||||
      }
 | 
			
		||||
      if (result > 0) {
 | 
			
		||||
        ESP_LOGD(TAG, "Nextion reported new range %d", result);
 | 
			
		||||
        this->content_length_ = this->tft_size_ - result;
 | 
			
		||||
        return result;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    recv_string.clear();
 | 
			
		||||
  }
 | 
			
		||||
  return range_end + 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::upload_tft() {
 | 
			
		||||
  if (this->is_updating_) {
 | 
			
		||||
    ESP_LOGD(TAG, "Currently updating");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!network_is_connected()) {
 | 
			
		||||
    ESP_LOGD(TAG, "network is not connected");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->is_updating_ = true;
 | 
			
		||||
 | 
			
		||||
  HTTPClient http;
 | 
			
		||||
  http.setTimeout(15000);  // Yes 15 seconds.... Helps 8266s along
 | 
			
		||||
  bool begin_status = false;
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
  begin_status = http.begin(this->tft_url_.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP8266
 | 
			
		||||
#ifndef CLANG_TIDY
 | 
			
		||||
  http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
 | 
			
		||||
  http.setRedirectLimit(3);
 | 
			
		||||
  begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (!begin_status) {
 | 
			
		||||
    this->is_updating_ = false;
 | 
			
		||||
    ESP_LOGD(TAG, "connection failed");
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
    if (psramFound())
 | 
			
		||||
      free(this->transfer_buffer_);
 | 
			
		||||
    else
 | 
			
		||||
#endif
 | 
			
		||||
      delete this->transfer_buffer_;
 | 
			
		||||
    return;
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGD(TAG, "Connected");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  http.addHeader("Range", "bytes=0-255");
 | 
			
		||||
  const char *header_names[] = {"Content-Range"};
 | 
			
		||||
  http.collectHeaders(header_names, 1);
 | 
			
		||||
  ESP_LOGD(TAG, "Requesting URL: %s", this->tft_url_.c_str());
 | 
			
		||||
 | 
			
		||||
  http.setReuse(true);
 | 
			
		||||
  // try up to 5 times. DNS sometimes needs a second try or so
 | 
			
		||||
  int tries = 1;
 | 
			
		||||
  int code = http.GET();
 | 
			
		||||
  delay(100);  // NOLINT
 | 
			
		||||
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
  while (code != 200 && code != 206 && tries <= 5) {
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retrying (%d/5)", this->tft_url_.c_str(),
 | 
			
		||||
             HTTPClient::errorToString(code).c_str(), tries);
 | 
			
		||||
 | 
			
		||||
    delay(250);  // NOLINT
 | 
			
		||||
    App.feed_wdt();
 | 
			
		||||
    code = http.GET();
 | 
			
		||||
    ++tries;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ((code != 200 && code != 206) || tries > 5) {
 | 
			
		||||
    this->upload_end_();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String content_range_string = http.header("Content-Range");
 | 
			
		||||
  content_range_string.remove(0, 12);
 | 
			
		||||
  this->content_length_ = content_range_string.toInt();
 | 
			
		||||
  this->tft_size_ = content_length_;
 | 
			
		||||
  http.end();
 | 
			
		||||
 | 
			
		||||
  if (this->content_length_ < 4096) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to get file size");
 | 
			
		||||
    this->upload_end_();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Updating Nextion %s...", this->device_model_.c_str());
 | 
			
		||||
  // The Nextion will ignore the update command if it is sleeping
 | 
			
		||||
 | 
			
		||||
  this->send_command_("sleep=0");
 | 
			
		||||
  this->set_backlight_brightness(1.0);
 | 
			
		||||
  delay(250);  // NOLINT
 | 
			
		||||
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
 | 
			
		||||
  char command[128];
 | 
			
		||||
  // Tells the Nextion the content length of the tft file and baud rate it will be sent at
 | 
			
		||||
  // Once the Nextion accepts the command it will wait until the file is successfully uploaded
 | 
			
		||||
  // If it fails for any reason a power cycle of the display will be needed
 | 
			
		||||
  sprintf(command, "whmi-wris %d,%d,1", this->content_length_, this->parent_->get_baud_rate());
 | 
			
		||||
 | 
			
		||||
  // Clear serial receive buffer
 | 
			
		||||
  uint8_t d;
 | 
			
		||||
  while (this->available()) {
 | 
			
		||||
    this->read_byte(&d);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  this->send_command_(command);
 | 
			
		||||
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
 | 
			
		||||
  std::string response;
 | 
			
		||||
  ESP_LOGD(TAG, "Waiting for upgrade response");
 | 
			
		||||
  this->recv_ret_string_(response, 2000, true);  // This can take some time to return
 | 
			
		||||
 | 
			
		||||
  // The Nextion display will, if it's ready to accept data, send a 0x05 byte.
 | 
			
		||||
  ESP_LOGD(TAG, "Upgrade response is %s %zu", response.c_str(), response.length());
 | 
			
		||||
 | 
			
		||||
  for (int i = 0; i < response.length(); i++) {
 | 
			
		||||
    ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (response.find(0x05) != std::string::npos) {
 | 
			
		||||
    ESP_LOGD(TAG, "preparation for tft update done");
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGD(TAG, "preparation for tft update failed %d \"%s\"", response[0], response.c_str());
 | 
			
		||||
    this->upload_end_();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
  uint32_t chunk_size = 8192;
 | 
			
		||||
  if (psramFound()) {
 | 
			
		||||
    chunk_size = this->content_length_;
 | 
			
		||||
  } else {
 | 
			
		||||
    if (ESP.getFreeHeap() > 40960) {  // 32K to keep on hand
 | 
			
		||||
      int chunk = int((ESP.getFreeHeap() - 32768) / 4096);
 | 
			
		||||
      chunk_size = chunk * 4096;
 | 
			
		||||
      chunk_size = chunk_size > 65536 ? 65536 : chunk_size;
 | 
			
		||||
    } else if (ESP.getFreeHeap() < 10240) {
 | 
			
		||||
      chunk_size = 4096;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#else
 | 
			
		||||
  uint32_t chunk_size = ESP.getFreeHeap() < 10240 ? 4096 : 8192;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (this->transfer_buffer_ == nullptr) {
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
    if (psramFound()) {
 | 
			
		||||
      ESP_LOGD(TAG, "Allocating PSRAM buffer size %d, Free PSRAM size is %u", chunk_size, ESP.getFreePsram());
 | 
			
		||||
      this->transfer_buffer_ = (uint8_t *) ps_malloc(chunk_size);
 | 
			
		||||
      if (this->transfer_buffer_ == nullptr) {
 | 
			
		||||
        ESP_LOGE(TAG, "Could not allocate buffer size %d!", chunk_size);
 | 
			
		||||
        this->upload_end_();
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
#endif
 | 
			
		||||
      ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap());
 | 
			
		||||
      this->transfer_buffer_ = new uint8_t[chunk_size];
 | 
			
		||||
      if (!this->transfer_buffer_) {  // Try a smaller size
 | 
			
		||||
        ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size);
 | 
			
		||||
        chunk_size = 4096;
 | 
			
		||||
        ESP_LOGD(TAG, "Allocating %d buffer", chunk_size);
 | 
			
		||||
        this->transfer_buffer_ = new uint8_t[chunk_size];
 | 
			
		||||
 | 
			
		||||
        if (!this->transfer_buffer_)
 | 
			
		||||
          this->upload_end_();
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
      }
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->transfer_buffer_size_ = chunk_size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d",
 | 
			
		||||
           this->tft_url_.c_str(), this->content_length_, this->transfer_buffer_size_, ESP.getFreeHeap());
 | 
			
		||||
 | 
			
		||||
  int result = 0;
 | 
			
		||||
  while (this->content_length_ > 0) {
 | 
			
		||||
    result = this->upload_by_chunks_(&http, result);
 | 
			
		||||
    if (result < 0) {
 | 
			
		||||
      ESP_LOGD(TAG, "Error updating Nextion!");
 | 
			
		||||
      this->upload_end_();
 | 
			
		||||
    }
 | 
			
		||||
    App.feed_wdt();
 | 
			
		||||
    ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), this->content_length_);
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Succesfully updated Nextion!");
 | 
			
		||||
 | 
			
		||||
  this->upload_end_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Nextion::upload_end_() {
 | 
			
		||||
  ESP_LOGD(TAG, "Restarting Nextion");
 | 
			
		||||
  this->soft_reset();
 | 
			
		||||
  delay(1500);  // NOLINT
 | 
			
		||||
  ESP_LOGD(TAG, "Restarting esphome");
 | 
			
		||||
  ESP.restart();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP8266
 | 
			
		||||
WiFiClient *Nextion::get_wifi_client_() {
 | 
			
		||||
  if (this->tft_url_.compare(0, 6, "https:") == 0) {
 | 
			
		||||
    if (this->wifi_client_secure_ == nullptr) {
 | 
			
		||||
      this->wifi_client_secure_ = new BearSSL::WiFiClientSecure();
 | 
			
		||||
      this->wifi_client_secure_->setInsecure();
 | 
			
		||||
      this->wifi_client_secure_->setBufferSizes(512, 512);
 | 
			
		||||
    }
 | 
			
		||||
    return this->wifi_client_secure_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->wifi_client_ == nullptr) {
 | 
			
		||||
    this->wifi_client_ = new WiFiClient();
 | 
			
		||||
  }
 | 
			
		||||
  return this->wifi_client_;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
void Nextion::upload_tft() { ESP_LOGW(TAG, "tft_url, WIFI or Ethernet components are needed. Cannot upload."); }
 | 
			
		||||
#endif
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										99
									
								
								esphome/components/nextion/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								esphome/components/nextion/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    UNIT_EMPTY,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    CONF_COMPONENT_ID,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
)
 | 
			
		||||
from .. import nextion_ns, CONF_NEXTION_ID
 | 
			
		||||
 | 
			
		||||
from ..base_component import (
 | 
			
		||||
    setup_component_core_,
 | 
			
		||||
    CONFIG_SENSOR_COMPONENT_SCHEMA,
 | 
			
		||||
    CONF_VARIABLE_NAME,
 | 
			
		||||
    CONF_COMPONENT_NAME,
 | 
			
		||||
    CONF_PRECISION,
 | 
			
		||||
    CONF_WAVE_CHANNEL_ID,
 | 
			
		||||
    CONF_WAVE_MAX_VALUE,
 | 
			
		||||
    CONF_WAVEFORM_SEND_LAST_VALUE,
 | 
			
		||||
    CONF_WAVE_MAX_LENGTH,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@senexcrenshaw"]
 | 
			
		||||
 | 
			
		||||
NextionSensor = nextion_ns.class_("NextionSensor", sensor.Sensor, cg.PollingComponent)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def CheckWaveID(value):
 | 
			
		||||
    value = cv.int_(value)
 | 
			
		||||
    if value < 0 or value > 3:
 | 
			
		||||
        raise cv.Invalid(f"Valid range for {CONF_WAVE_CHANNEL_ID} is 0-3")
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _validate(config):
 | 
			
		||||
    if CONF_WAVE_CHANNEL_ID in config and CONF_COMPONENT_ID not in config:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"{CONF_COMPONENT_ID} is required when {CONF_WAVE_CHANNEL_ID} is set"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_EMPTY)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(NextionSensor),
 | 
			
		||||
            cv.Optional(CONF_PRECISION, default=0): cv.int_range(min=0, max=8),
 | 
			
		||||
            cv.Optional(CONF_WAVE_CHANNEL_ID): CheckWaveID,
 | 
			
		||||
            cv.Optional(CONF_COMPONENT_ID): cv.uint8_t,
 | 
			
		||||
            cv.Optional(CONF_WAVE_MAX_LENGTH, default=255): cv.int_range(
 | 
			
		||||
                min=1, max=1024
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_WAVE_MAX_VALUE, default=100): cv.int_range(
 | 
			
		||||
                min=1, max=1024
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_WAVEFORM_SEND_LAST_VALUE, default=True): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(CONFIG_SENSOR_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(cv.polling_component_schema("never")),
 | 
			
		||||
    cv.has_exactly_one_key(CONF_COMPONENT_ID, CONF_COMPONENT_NAME, CONF_VARIABLE_NAME),
 | 
			
		||||
    _validate,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_NEXTION_ID])
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], hub)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await sensor.register_sensor(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(hub.register_sensor_component(var))
 | 
			
		||||
 | 
			
		||||
    await setup_component_core_(var, config, ".val")
 | 
			
		||||
 | 
			
		||||
    if CONF_PRECISION in config:
 | 
			
		||||
        cg.add(var.set_precision(config[CONF_PRECISION]))
 | 
			
		||||
 | 
			
		||||
    if CONF_COMPONENT_ID in config:
 | 
			
		||||
        cg.add(var.set_component_id(config[CONF_COMPONENT_ID]))
 | 
			
		||||
 | 
			
		||||
    if CONF_WAVE_CHANNEL_ID in config:
 | 
			
		||||
        cg.add(var.set_wave_channel_id(config[CONF_WAVE_CHANNEL_ID]))
 | 
			
		||||
 | 
			
		||||
    if CONF_WAVEFORM_SEND_LAST_VALUE in config:
 | 
			
		||||
        cg.add(var.set_waveform_send_last_value(config[CONF_WAVEFORM_SEND_LAST_VALUE]))
 | 
			
		||||
 | 
			
		||||
    if CONF_WAVE_MAX_VALUE in config:
 | 
			
		||||
        cg.add(var.set_wave_max_value(config[CONF_WAVE_MAX_VALUE]))
 | 
			
		||||
 | 
			
		||||
    if CONF_WAVE_MAX_LENGTH in config:
 | 
			
		||||
        cg.add(var.set_wave_max_length(config[CONF_WAVE_MAX_LENGTH]))
 | 
			
		||||
							
								
								
									
										110
									
								
								esphome/components/nextion/sensor/nextion_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								esphome/components/nextion/sensor/nextion_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
#include "nextion_sensor.h"
 | 
			
		||||
#include "esphome/core/util.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "nextion_sensor";
 | 
			
		||||
 | 
			
		||||
void NextionSensor::process_sensor(const std::string &variable_name, int state) {
 | 
			
		||||
  if (!this->nextion_->is_setup())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (this->wave_chan_id_ == UINT8_MAX && this->variable_name_ == variable_name) {
 | 
			
		||||
    this->publish_state(state);
 | 
			
		||||
    ESP_LOGD(TAG, "Processed sensor \"%s\" state %d", variable_name.c_str(), state);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionSensor::add_to_wave_buffer(float state) {
 | 
			
		||||
  this->needs_to_send_update_ = true;
 | 
			
		||||
 | 
			
		||||
  int wave_state = (int) ((state / (float) this->wave_maxvalue_) * 100);
 | 
			
		||||
 | 
			
		||||
  wave_buffer_.push_back(wave_state);
 | 
			
		||||
 | 
			
		||||
  if (this->wave_buffer_.size() > this->wave_max_length_) {
 | 
			
		||||
    this->wave_buffer_.erase(this->wave_buffer_.begin());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionSensor::update() {
 | 
			
		||||
  if (!this->nextion_->is_setup())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (this->wave_chan_id_ == UINT8_MAX) {
 | 
			
		||||
    this->nextion_->add_to_get_queue(this);
 | 
			
		||||
  } else {
 | 
			
		||||
    if (this->send_last_value_) {
 | 
			
		||||
      this->add_to_wave_buffer(this->last_value_);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->wave_update_();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) {
 | 
			
		||||
  if (!this->nextion_->is_setup())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (isnan(state))
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (this->wave_chan_id_ == UINT8_MAX) {
 | 
			
		||||
    if (send_to_nextion) {
 | 
			
		||||
      if (this->nextion_->is_sleeping() || !this->visible_) {
 | 
			
		||||
        this->needs_to_send_update_ = true;
 | 
			
		||||
      } else {
 | 
			
		||||
        this->needs_to_send_update_ = false;
 | 
			
		||||
 | 
			
		||||
        if (this->precision_ > 0) {
 | 
			
		||||
          double to_multiply = pow(10, this->precision_);
 | 
			
		||||
          int state_value = (int) (state * to_multiply);
 | 
			
		||||
 | 
			
		||||
          this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value);
 | 
			
		||||
        } else {
 | 
			
		||||
          this->nextion_->add_no_result_to_queue_with_set(this, (int) state);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    if (this->send_last_value_) {
 | 
			
		||||
      this->last_value_ = state;  // Update will handle setting the buffer
 | 
			
		||||
    } else {
 | 
			
		||||
      this->add_to_wave_buffer(state);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->wave_chan_id_ == UINT8_MAX) {
 | 
			
		||||
    if (publish) {
 | 
			
		||||
      this->publish_state(state);
 | 
			
		||||
    } else {
 | 
			
		||||
      this->raw_state = state;
 | 
			
		||||
      this->state = state;
 | 
			
		||||
      this->has_state_ = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  this->update_component_settings();
 | 
			
		||||
 | 
			
		||||
  ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %lf", this->variable_name_.c_str(), state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionSensor::wave_update_() {
 | 
			
		||||
  if (this->nextion_->is_sleeping() || this->wave_buffer_.empty()) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef NEXTION_PROTOCOL_LOG
 | 
			
		||||
  size_t buffer_to_send =
 | 
			
		||||
      this->wave_buffer_.size() < 255 ? this->wave_buffer_.size() : 255;  // ADDT command can only send 255
 | 
			
		||||
 | 
			
		||||
  ESP_LOGN(TAG, "wave_update send %zu of %zu value(s) to wave nextion component id %d and wave channel id %d",
 | 
			
		||||
           buffer_to_send, this->wave_buffer_.size(), this->component_id_, this->wave_chan_id_);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  this->nextion_->add_addt_command_to_queue(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										49
									
								
								esphome/components/nextion/sensor/nextion_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								esphome/components/nextion/sensor/nextion_sensor.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "../nextion_component.h"
 | 
			
		||||
#include "../nextion_base.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
class NextionSensor;
 | 
			
		||||
 | 
			
		||||
class NextionSensor : public NextionComponent, public sensor::Sensor, public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  NextionSensor(NextionBase *nextion) { this->nextion_ = nextion; }
 | 
			
		||||
  void send_state_to_nextion() override { this->set_state(this->state, false, true); };
 | 
			
		||||
 | 
			
		||||
  void update_component() override { this->update(); }
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void add_to_wave_buffer(float state);
 | 
			
		||||
  void set_precision(uint8_t precision) { this->precision_ = precision; }
 | 
			
		||||
  void set_component_id(uint8_t component_id) { component_id_ = component_id; }
 | 
			
		||||
  void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; }
 | 
			
		||||
  void set_wave_max_value(uint32_t wave_maxvalue) { this->wave_maxvalue_ = wave_maxvalue; }
 | 
			
		||||
  void process_sensor(const std::string &variable_name, int state) override;
 | 
			
		||||
 | 
			
		||||
  void set_state(float state) override { this->set_state(state, true, true); }
 | 
			
		||||
  void set_state(float state, bool publish) override { this->set_state(state, publish, true); }
 | 
			
		||||
  void set_state(float state, bool publish, bool send_to_nextion) override;
 | 
			
		||||
 | 
			
		||||
  void set_waveform_send_last_value(bool send_last_value) { this->send_last_value_ = send_last_value; }
 | 
			
		||||
  uint8_t get_wave_chan_id() { return this->wave_chan_id_; }
 | 
			
		||||
  void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; }
 | 
			
		||||
  NextionQueueType get_queue_type() override {
 | 
			
		||||
    return this->wave_chan_id_ == UINT8_MAX ? NextionQueueType::SENSOR : NextionQueueType::WAVEFORM_SENSOR;
 | 
			
		||||
  }
 | 
			
		||||
  void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {}
 | 
			
		||||
  void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {
 | 
			
		||||
    this->set_state(state_value, publish, send_to_nextion);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t precision_ = 0;
 | 
			
		||||
  uint32_t wave_maxvalue_ = 255;
 | 
			
		||||
 | 
			
		||||
  float last_value_ = 0;
 | 
			
		||||
  bool send_last_value_ = true;
 | 
			
		||||
  void wave_update_();
 | 
			
		||||
};
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										39
									
								
								esphome/components/nextion/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								esphome/components/nextion/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import switch
 | 
			
		||||
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
from .. import nextion_ns, CONF_NEXTION_ID
 | 
			
		||||
 | 
			
		||||
from ..base_component import (
 | 
			
		||||
    setup_component_core_,
 | 
			
		||||
    CONF_COMPONENT_NAME,
 | 
			
		||||
    CONF_VARIABLE_NAME,
 | 
			
		||||
    CONFIG_SWITCH_COMPONENT_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@senexcrenshaw"]
 | 
			
		||||
 | 
			
		||||
NextionSwitch = nextion_ns.class_("NextionSwitch", switch.Switch, cg.PollingComponent)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    switch.SWITCH_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(NextionSwitch),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(CONFIG_SWITCH_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(cv.polling_component_schema("never")),
 | 
			
		||||
    cv.has_exactly_one_key(CONF_COMPONENT_NAME, CONF_VARIABLE_NAME),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_NEXTION_ID])
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], hub)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await switch.register_switch(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(hub.register_switch_component(var))
 | 
			
		||||
 | 
			
		||||
    await setup_component_core_(var, config, ".val")
 | 
			
		||||
							
								
								
									
										52
									
								
								esphome/components/nextion/switch/nextion_switch.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								esphome/components/nextion/switch/nextion_switch.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
#include "nextion_switch.h"
 | 
			
		||||
#include "esphome/core/util.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "nextion_switch";
 | 
			
		||||
 | 
			
		||||
void NextionSwitch::process_bool(const std::string &variable_name, bool on) {
 | 
			
		||||
  if (!this->nextion_->is_setup())
 | 
			
		||||
    return;
 | 
			
		||||
  if (this->variable_name_ == variable_name) {
 | 
			
		||||
    this->publish_state(on);
 | 
			
		||||
 | 
			
		||||
    ESP_LOGD(TAG, "Processed switch \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionSwitch::update() {
 | 
			
		||||
  if (!this->nextion_->is_setup())
 | 
			
		||||
    return;
 | 
			
		||||
  this->nextion_->add_to_get_queue(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) {
 | 
			
		||||
  if (!this->nextion_->is_setup())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (send_to_nextion) {
 | 
			
		||||
    if (this->nextion_->is_sleeping() || !this->visible_) {
 | 
			
		||||
      this->needs_to_send_update_ = true;
 | 
			
		||||
    } else {
 | 
			
		||||
      this->needs_to_send_update_ = false;
 | 
			
		||||
      this->nextion_->add_no_result_to_queue_with_set(this, (int) state);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (publish) {
 | 
			
		||||
    this->publish_state(state);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->state = state;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->update_component_settings();
 | 
			
		||||
 | 
			
		||||
  ESP_LOGN(TAG, "Updated switch \"%s\" state %s", this->variable_name_.c_str(), ONOFF(state));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionSwitch::write_state(bool state) { this->set_state(state); }
 | 
			
		||||
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										34
									
								
								esphome/components/nextion/switch/nextion_switch.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								esphome/components/nextion/switch/nextion_switch.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/switch/switch.h"
 | 
			
		||||
#include "../nextion_component.h"
 | 
			
		||||
#include "../nextion_base.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
class NextionSwitch;
 | 
			
		||||
 | 
			
		||||
class NextionSwitch : public NextionComponent, public switch_::Switch, public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  NextionSwitch(NextionBase *nextion) { this->nextion_ = nextion; }
 | 
			
		||||
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void update_component() override { this->update(); }
 | 
			
		||||
  void process_bool(const std::string &variable_name, bool on) override;
 | 
			
		||||
 | 
			
		||||
  void set_state(bool state) override { this->set_state(state, true, true); }
 | 
			
		||||
  void set_state(bool state, bool publish) override { this->set_state(state, publish, true); }
 | 
			
		||||
  void set_state(bool state, bool publish, bool send_to_nextion) override;
 | 
			
		||||
 | 
			
		||||
  void send_state_to_nextion() override { this->set_state(this->state, false, true); };
 | 
			
		||||
  NextionQueueType get_queue_type() override { return NextionQueueType::SWITCH; }
 | 
			
		||||
  void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {}
 | 
			
		||||
  void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {
 | 
			
		||||
    this->set_state(state_value != 0, publish, send_to_nextion);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void write_state(bool state) override;
 | 
			
		||||
};
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										38
									
								
								esphome/components/nextion/text_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								esphome/components/nextion/text_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
from esphome.components import text_sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
from .. import nextion_ns, CONF_NEXTION_ID
 | 
			
		||||
 | 
			
		||||
from ..base_component import (
 | 
			
		||||
    setup_component_core_,
 | 
			
		||||
    CONFIG_TEXT_COMPONENT_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@senexcrenshaw"]
 | 
			
		||||
 | 
			
		||||
NextionTextSensor = nextion_ns.class_(
 | 
			
		||||
    "NextionTextSensor", text_sensor.TextSensor, cg.PollingComponent
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    text_sensor.TEXT_SENSOR_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(NextionTextSensor),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(CONFIG_TEXT_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(cv.polling_component_schema("never"))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_NEXTION_ID])
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], hub)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await text_sensor.register_text_sensor(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(hub.register_textsensor_component(var))
 | 
			
		||||
 | 
			
		||||
    await setup_component_core_(var, config, ".txt")
 | 
			
		||||
@@ -0,0 +1,49 @@
 | 
			
		||||
#include "nextion_textsensor.h"
 | 
			
		||||
#include "esphome/core/util.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
static const char *const TAG = "nextion_textsensor";
 | 
			
		||||
 | 
			
		||||
void NextionTextSensor::process_text(const std::string &variable_name, const std::string &text_value) {
 | 
			
		||||
  if (!this->nextion_->is_setup())
 | 
			
		||||
    return;
 | 
			
		||||
  if (this->variable_name_ == variable_name) {
 | 
			
		||||
    this->publish_state(text_value);
 | 
			
		||||
    ESP_LOGD(TAG, "Processed text_sensor \"%s\" state \"%s\"", variable_name.c_str(), text_value.c_str());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionTextSensor::update() {
 | 
			
		||||
  if (!this->nextion_->is_setup())
 | 
			
		||||
    return;
 | 
			
		||||
  this->nextion_->add_to_get_queue(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NextionTextSensor::set_state(const std::string &state, bool publish, bool send_to_nextion) {
 | 
			
		||||
  if (!this->nextion_->is_setup())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (send_to_nextion) {
 | 
			
		||||
    if (this->nextion_->is_sleeping() || !this->visible_) {
 | 
			
		||||
      this->needs_to_send_update_ = true;
 | 
			
		||||
    } else {
 | 
			
		||||
      this->nextion_->add_no_result_to_queue_with_set(this, state);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (publish) {
 | 
			
		||||
    this->publish_state(state);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->state = state;
 | 
			
		||||
    this->has_state_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->update_component_settings();
 | 
			
		||||
 | 
			
		||||
  ESP_LOGN(TAG, "Wrote state for text_sensor \"%s\" state \"%s\"", this->variable_name_.c_str(), state.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										32
									
								
								esphome/components/nextion/text_sensor/nextion_textsensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								esphome/components/nextion/text_sensor/nextion_textsensor.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/text_sensor/text_sensor.h"
 | 
			
		||||
#include "../nextion_component.h"
 | 
			
		||||
#include "../nextion_base.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace nextion {
 | 
			
		||||
class NextionTextSensor;
 | 
			
		||||
 | 
			
		||||
class NextionTextSensor : public NextionComponent, public text_sensor::TextSensor, public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  NextionTextSensor(NextionBase *nextion) { this->nextion_ = nextion; }
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void update_component() override { this->update(); }
 | 
			
		||||
  void on_state_changed(const std::string &state);
 | 
			
		||||
 | 
			
		||||
  void process_text(const std::string &variable_name, const std::string &text_value) override;
 | 
			
		||||
 | 
			
		||||
  void set_state(const std::string &state, bool publish) override { this->set_state(state, publish, true); }
 | 
			
		||||
  void set_state(const std::string &state) override { this->set_state(state, true, true); }
 | 
			
		||||
  void set_state(const std::string &state, bool publish, bool send_to_nextion) override;
 | 
			
		||||
 | 
			
		||||
  void send_state_to_nextion() override { this->set_state(this->state, false, true); };
 | 
			
		||||
  NextionQueueType get_queue_type() override { return NextionQueueType::TEXT_SENSOR; }
 | 
			
		||||
  void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {}
 | 
			
		||||
  void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {
 | 
			
		||||
    this->set_state(state_value, publish, send_to_nextion);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
}  // namespace nextion
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
from typing import Optional
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
@@ -66,12 +67,18 @@ NUMBER_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_number_core_(var, config):
 | 
			
		||||
async def setup_number_core_(
 | 
			
		||||
    var, config, *, min_value: float, max_value: float, step: Optional[float]
 | 
			
		||||
):
 | 
			
		||||
    cg.add(var.set_name(config[CONF_NAME]))
 | 
			
		||||
    if CONF_INTERNAL in config:
 | 
			
		||||
        cg.add(var.set_internal(config[CONF_INTERNAL]))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_icon(config[CONF_ICON]))
 | 
			
		||||
    cg.add(var.traits.set_icon(config[CONF_ICON]))
 | 
			
		||||
    cg.add(var.traits.set_min_value(min_value))
 | 
			
		||||
    cg.add(var.traits.set_max_value(max_value))
 | 
			
		||||
    if step is not None:
 | 
			
		||||
        cg.add(var.traits.set_step(step))
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_VALUE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
@@ -92,16 +99,24 @@ async def setup_number_core_(var, config):
 | 
			
		||||
        await mqtt.register_mqtt_component(mqtt_, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def register_number(var, config):
 | 
			
		||||
async def register_number(
 | 
			
		||||
    var, config, *, min_value: float, max_value: float, step: Optional[float] = None
 | 
			
		||||
):
 | 
			
		||||
    if not CORE.has_id(config[CONF_ID]):
 | 
			
		||||
        var = cg.Pvariable(config[CONF_ID], var)
 | 
			
		||||
    cg.add(cg.App.register_number(var))
 | 
			
		||||
    await setup_number_core_(var, config)
 | 
			
		||||
    await setup_number_core_(
 | 
			
		||||
        var, config, min_value=min_value, max_value=max_value, step=step
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def new_number(config):
 | 
			
		||||
async def new_number(
 | 
			
		||||
    config, *, min_value: float, max_value: float, step: Optional[float] = None
 | 
			
		||||
):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await register_number(var, config)
 | 
			
		||||
    await register_number(
 | 
			
		||||
        var, config, min_value=min_value, max_value=max_value, step=step
 | 
			
		||||
    )
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,67 +8,38 @@ static const char *const TAG = "number";
 | 
			
		||||
 | 
			
		||||
void NumberCall::perform() {
 | 
			
		||||
  ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
 | 
			
		||||
  if (this->value_.has_value()) {
 | 
			
		||||
    auto value = *this->value_;
 | 
			
		||||
    uint8_t accuracy = this->parent_->get_accuracy_decimals();
 | 
			
		||||
    float min_value = this->parent_->get_min_value();
 | 
			
		||||
    if (value < min_value) {
 | 
			
		||||
      ESP_LOGW(TAG, "  Value %s must not be less than minimum %s", value_accuracy_to_string(value, accuracy).c_str(),
 | 
			
		||||
               value_accuracy_to_string(min_value, accuracy).c_str());
 | 
			
		||||
      this->value_.reset();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    float max_value = this->parent_->get_max_value();
 | 
			
		||||
    if (value > max_value) {
 | 
			
		||||
      ESP_LOGW(TAG, "  Value %s must not be larger than maximum %s", value_accuracy_to_string(value, accuracy).c_str(),
 | 
			
		||||
               value_accuracy_to_string(max_value, accuracy).c_str());
 | 
			
		||||
      this->value_.reset();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    ESP_LOGD(TAG, "  Value: %s", value_accuracy_to_string(*this->value_, accuracy).c_str());
 | 
			
		||||
    this->parent_->set(*this->value_);
 | 
			
		||||
  if (!this->value_.has_value() || isnan(*this->value_)) {
 | 
			
		||||
    ESP_LOGW(TAG, "No value set for NumberCall");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const auto &traits = this->parent_->traits;
 | 
			
		||||
  auto value = *this->value_;
 | 
			
		||||
 | 
			
		||||
  float min_value = traits.get_min_value();
 | 
			
		||||
  if (value < min_value) {
 | 
			
		||||
    ESP_LOGW(TAG, "  Value %f must not be less than minimum %f", value, min_value);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  float max_value = traits.get_max_value();
 | 
			
		||||
  if (value > max_value) {
 | 
			
		||||
    ESP_LOGW(TAG, "  Value %f must not be greater than maximum %f", value, max_value);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "  Value: %f", *this->value_);
 | 
			
		||||
  this->parent_->control(*this->value_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NumberCall &NumberCall::set_value(float value) {
 | 
			
		||||
  this->value_ = value;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const optional<float> &NumberCall::get_value() const { return this->value_; }
 | 
			
		||||
 | 
			
		||||
NumberCall Number::make_call() { return NumberCall(this); }
 | 
			
		||||
 | 
			
		||||
void Number::publish_state(float state) {
 | 
			
		||||
  this->has_state_ = true;
 | 
			
		||||
  this->state = state;
 | 
			
		||||
  ESP_LOGD(TAG, "'%s': Sending state %.5f", this->get_name().c_str(), state);
 | 
			
		||||
  ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state);
 | 
			
		||||
  this->state_callback_.call(state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t Number::update_interval() { return 0; }
 | 
			
		||||
Number::Number(const std::string &name) : Nameable(name), state(NAN) {}
 | 
			
		||||
Number::Number() : Number("") {}
 | 
			
		||||
 | 
			
		||||
void Number::add_on_state_callback(std::function<void(float)> &&callback) {
 | 
			
		||||
  this->state_callback_.add(std::move(callback));
 | 
			
		||||
}
 | 
			
		||||
void Number::set_icon(const std::string &icon) { this->icon_ = icon; }
 | 
			
		||||
std::string Number::get_icon() { return *this->icon_; }
 | 
			
		||||
int8_t Number::get_accuracy_decimals() {
 | 
			
		||||
  // use printf %g to find number of digits based on step
 | 
			
		||||
  char buf[32];
 | 
			
		||||
  sprintf(buf, "%.5g", this->step_);
 | 
			
		||||
  std::string str{buf};
 | 
			
		||||
  size_t dot_pos = str.find('.');
 | 
			
		||||
  if (dot_pos == std::string::npos)
 | 
			
		||||
    return 0;
 | 
			
		||||
 | 
			
		||||
  return str.length() - dot_pos - 1;
 | 
			
		||||
}
 | 
			
		||||
float Number::get_state() const { return this->state; }
 | 
			
		||||
 | 
			
		||||
bool Number::has_state() const { return this->has_state_; }
 | 
			
		||||
 | 
			
		||||
uint32_t Number::hash_base() { return 2282307003UL; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,8 @@ namespace number {
 | 
			
		||||
#define LOG_NUMBER(prefix, type, obj) \
 | 
			
		||||
  if ((obj) != nullptr) { \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
 | 
			
		||||
    if (!(obj)->get_icon().empty()) { \
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "%s  Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
 | 
			
		||||
    if (!(obj)->traits.get_icon().empty()) { \
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "%s  Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \
 | 
			
		||||
    } \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -19,72 +19,56 @@ class Number;
 | 
			
		||||
class NumberCall {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit NumberCall(Number *parent) : parent_(parent) {}
 | 
			
		||||
  NumberCall &set_value(float value);
 | 
			
		||||
  void perform();
 | 
			
		||||
 | 
			
		||||
  const optional<float> &get_value() const;
 | 
			
		||||
  NumberCall &set_value(float value) {
 | 
			
		||||
    value_ = value;
 | 
			
		||||
    return *this;
 | 
			
		||||
  }
 | 
			
		||||
  const optional<float> &get_value() const { return value_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Number *const parent_;
 | 
			
		||||
  optional<float> value_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class NumberTraits {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_min_value(float min_value) { min_value_ = min_value; }
 | 
			
		||||
  float get_min_value() const { return min_value_; }
 | 
			
		||||
  void set_max_value(float max_value) { max_value_ = max_value; }
 | 
			
		||||
  float get_max_value() const { return max_value_; }
 | 
			
		||||
  void set_step(float step) { step_ = step; }
 | 
			
		||||
  float get_step() const { return step_; }
 | 
			
		||||
  void set_icon(std::string icon) { icon_ = std::move(icon); }
 | 
			
		||||
  const std::string &get_icon() const { return icon_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  float min_value_ = NAN;
 | 
			
		||||
  float max_value_ = NAN;
 | 
			
		||||
  float step_ = NAN;
 | 
			
		||||
  std::string icon_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** Base-class for all numbers.
 | 
			
		||||
 *
 | 
			
		||||
 * A number can use publish_state to send out a new value.
 | 
			
		||||
 */
 | 
			
		||||
class Number : public Nameable {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit Number();
 | 
			
		||||
  explicit Number(const std::string &name);
 | 
			
		||||
 | 
			
		||||
  /** Manually set the icon of this number. By default the number's default defined by icon() is used.
 | 
			
		||||
   *
 | 
			
		||||
   * @param icon The icon, for example "mdi:flash". "" to disable.
 | 
			
		||||
   */
 | 
			
		||||
  void set_icon(const std::string &icon);
 | 
			
		||||
  /// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead.
 | 
			
		||||
  std::string get_icon();
 | 
			
		||||
 | 
			
		||||
  /// Getter-syntax for .state.
 | 
			
		||||
  float get_state() const;
 | 
			
		||||
 | 
			
		||||
  /// Get the accuracy in decimals. Based on the step value.
 | 
			
		||||
  int8_t get_accuracy_decimals();
 | 
			
		||||
 | 
			
		||||
  /** Publish the current state to the front-end.
 | 
			
		||||
   */
 | 
			
		||||
  void publish_state(float state);
 | 
			
		||||
 | 
			
		||||
  NumberCall make_call();
 | 
			
		||||
 | 
			
		||||
  // ========== INTERNAL METHODS ==========
 | 
			
		||||
  // (In most use cases you won't need these)
 | 
			
		||||
  /// Add a callback that will be called every time the state changes.
 | 
			
		||||
  void add_on_state_callback(std::function<void(float)> &&callback);
 | 
			
		||||
 | 
			
		||||
  /** This member variable stores the last state.
 | 
			
		||||
   *
 | 
			
		||||
   * On startup, when no state is available yet, this is NAN (not-a-number) and the validity
 | 
			
		||||
   * can be checked using has_state().
 | 
			
		||||
   *
 | 
			
		||||
   * This is exposed through a member variable for ease of use in esphome lambdas.
 | 
			
		||||
   */
 | 
			
		||||
  float state;
 | 
			
		||||
 | 
			
		||||
  void publish_state(float state);
 | 
			
		||||
 | 
			
		||||
  NumberCall make_call() { return NumberCall(this); }
 | 
			
		||||
  void set(float value) { make_call().set_value(value).perform(); }
 | 
			
		||||
 | 
			
		||||
  void add_on_state_callback(std::function<void(float)> &&callback);
 | 
			
		||||
 | 
			
		||||
  NumberTraits traits;
 | 
			
		||||
 | 
			
		||||
  /// Return whether this number has gotten a full state yet.
 | 
			
		||||
  bool has_state() const;
 | 
			
		||||
 | 
			
		||||
  /// Return with which interval the number is polled. Return 0 for non-polling mode.
 | 
			
		||||
  virtual uint32_t update_interval();
 | 
			
		||||
 | 
			
		||||
  void set_min_value(float min_value) { this->min_value_ = min_value; }
 | 
			
		||||
  void set_max_value(float max_value) { this->max_value_ = max_value; }
 | 
			
		||||
  void set_step(float step) { this->step_ = step; }
 | 
			
		||||
 | 
			
		||||
  float get_min_value() const { return this->min_value_; }
 | 
			
		||||
  float get_max_value() const { return this->max_value_; }
 | 
			
		||||
  float get_step() const { return this->step_; }
 | 
			
		||||
  bool has_state() const { return has_state_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  friend class NumberCall;
 | 
			
		||||
@@ -95,17 +79,12 @@ class Number : public Nameable {
 | 
			
		||||
   *
 | 
			
		||||
   * @param value The value as validated by the NumberCall.
 | 
			
		||||
   */
 | 
			
		||||
  virtual void set(float value) = 0;
 | 
			
		||||
  virtual void control(float value) = 0;
 | 
			
		||||
 | 
			
		||||
  uint32_t hash_base() override;
 | 
			
		||||
 | 
			
		||||
  CallbackManager<void(float)> state_callback_;
 | 
			
		||||
  /// Override the icon advertised to Home Assistant, otherwise number's icon will be used.
 | 
			
		||||
  optional<std::string> icon_;
 | 
			
		||||
  bool has_state_{false};
 | 
			
		||||
  float step_{1.0};
 | 
			
		||||
  float min_value_{0};
 | 
			
		||||
  float max_value_{100};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace number
 | 
			
		||||
 
 | 
			
		||||
@@ -35,9 +35,9 @@ void PIDClimate::control(const climate::ClimateCall &call) {
 | 
			
		||||
  if (call.get_target_temperature().has_value())
 | 
			
		||||
    this->target_temperature = *call.get_target_temperature();
 | 
			
		||||
 | 
			
		||||
  // If switching to non-auto mode, set output immediately
 | 
			
		||||
  if (this->mode != climate::CLIMATE_MODE_HEAT_COOL)
 | 
			
		||||
    this->handle_non_auto_mode_();
 | 
			
		||||
  // If switching to off mode, set output immediately
 | 
			
		||||
  if (this->mode == climate::CLIMATE_MODE_OFF)
 | 
			
		||||
    this->write_output_(0.0f);
 | 
			
		||||
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
}
 | 
			
		||||
@@ -98,15 +98,6 @@ void PIDClimate::write_output_(float value) {
 | 
			
		||||
  }
 | 
			
		||||
  this->pid_computed_callback_.call();
 | 
			
		||||
}
 | 
			
		||||
void PIDClimate::handle_non_auto_mode_() {
 | 
			
		||||
  // in non-auto mode, switch directly to appropriate action
 | 
			
		||||
  //  - OFF mode -> Output at 0%
 | 
			
		||||
  if (this->mode == climate::CLIMATE_MODE_OFF) {
 | 
			
		||||
    this->write_output_(0.0);
 | 
			
		||||
  } else {
 | 
			
		||||
    assert(false);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void PIDClimate::update_pid_() {
 | 
			
		||||
  float value;
 | 
			
		||||
  if (isnan(this->current_temperature) || isnan(this->target_temperature)) {
 | 
			
		||||
@@ -135,7 +126,7 @@ void PIDClimate::update_pid_() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->mode == climate::CLIMATE_MODE_OFF) {
 | 
			
		||||
    this->handle_non_auto_mode_();
 | 
			
		||||
    this->write_output_(0.0);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->write_output_(value);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,6 @@ class PIDClimate : public climate::Climate, public Component {
 | 
			
		||||
  bool supports_heat_() const { return this->heat_output_ != nullptr; }
 | 
			
		||||
 | 
			
		||||
  void write_output_(float value);
 | 
			
		||||
  void handle_non_auto_mode_();
 | 
			
		||||
 | 
			
		||||
  /// The sensor used for getting the current temperature
 | 
			
		||||
  sensor::Sensor *sensor_;
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,8 @@ from esphome.const import (
 | 
			
		||||
    CONF_TOTAL,
 | 
			
		||||
    CONF_VALUE,
 | 
			
		||||
    ICON_PULSE,
 | 
			
		||||
    LAST_RESET_TYPE_AUTO,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    STATE_CLASS_NONE,
 | 
			
		||||
    UNIT_PULSES,
 | 
			
		||||
    UNIT_PULSES_PER_MINUTE,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
@@ -59,7 +59,12 @@ CONFIG_SCHEMA = sensor.sensor_schema(
 | 
			
		||||
        cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter,
 | 
			
		||||
        cv.Optional(CONF_TIMEOUT, default="5min"): validate_timeout,
 | 
			
		||||
        cv.Optional(CONF_TOTAL): sensor.sensor_schema(
 | 
			
		||||
            UNIT_PULSES, ICON_PULSE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
 | 
			
		||||
            UNIT_PULSES,
 | 
			
		||||
            ICON_PULSE,
 | 
			
		||||
            0,
 | 
			
		||||
            DEVICE_CLASS_EMPTY,
 | 
			
		||||
            STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            LAST_RESET_TYPE_AUTO,
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    LAST_RESET_TYPE_AUTO,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    STATE_CLASS_NONE,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
@@ -47,7 +47,8 @@ CONFIG_SCHEMA = (
 | 
			
		||||
                ICON_EMPTY,
 | 
			
		||||
                0,
 | 
			
		||||
                DEVICE_CLASS_ENERGY,
 | 
			
		||||
                STATE_CLASS_NONE,
 | 
			
		||||
                STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                LAST_RESET_TYPE_AUTO,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -17,8 +17,8 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_ENERGY,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    ICON_CURRENT_AC,
 | 
			
		||||
    LAST_RESET_TYPE_AUTO,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    STATE_CLASS_NONE,
 | 
			
		||||
    UNIT_HERTZ,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
@@ -54,7 +54,8 @@ CONFIG_SCHEMA = (
 | 
			
		||||
                ICON_EMPTY,
 | 
			
		||||
                0,
 | 
			
		||||
                DEVICE_CLASS_ENERGY,
 | 
			
		||||
                STATE_CLASS_NONE,
 | 
			
		||||
                STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                LAST_RESET_TYPE_AUTO,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
 | 
			
		||||
                UNIT_HERTZ,
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
 | 
			
		||||
  bool initialized_{false};
 | 
			
		||||
  std::vector<rmt_item32_t> rmt_temp_;
 | 
			
		||||
  esp_err_t error_code_{ESP_OK};
 | 
			
		||||
  bool inverted_{false};
 | 
			
		||||
#endif
 | 
			
		||||
  uint8_t carrier_duty_percent_{50};
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ namespace remote_transmitter {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "remote_transmitter";
 | 
			
		||||
 | 
			
		||||
void RemoteTransmitterComponent::setup() {}
 | 
			
		||||
void RemoteTransmitterComponent::setup() { this->configure_rmt(); }
 | 
			
		||||
 | 
			
		||||
void RemoteTransmitterComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Remote Transmitter...");
 | 
			
		||||
@@ -50,6 +50,7 @@ void RemoteTransmitterComponent::configure_rmt() {
 | 
			
		||||
  } else {
 | 
			
		||||
    c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
 | 
			
		||||
    c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH;
 | 
			
		||||
    this->inverted_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  esp_err_t error = rmt_config(&c);
 | 
			
		||||
@@ -95,10 +96,10 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
 | 
			
		||||
      val -= item;
 | 
			
		||||
 | 
			
		||||
      if (rmt_i % 2 == 0) {
 | 
			
		||||
        rmt_item.level0 = static_cast<uint32_t>(level);
 | 
			
		||||
        rmt_item.level0 = static_cast<uint32_t>(level ^ this->inverted_);
 | 
			
		||||
        rmt_item.duration0 = static_cast<uint32_t>(item);
 | 
			
		||||
      } else {
 | 
			
		||||
        rmt_item.level1 = static_cast<uint32_t>(level);
 | 
			
		||||
        rmt_item.level1 = static_cast<uint32_t>(level ^ this->inverted_);
 | 
			
		||||
        rmt_item.duration1 = static_cast<uint32_t>(item);
 | 
			
		||||
        this->rmt_temp_.push_back(rmt_item);
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,8 @@ from esphome.const import (
 | 
			
		||||
    ICON_CURRENT_AC,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    ICON_FLASH,
 | 
			
		||||
    LAST_RESET_TYPE_AUTO,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    STATE_CLASS_NONE,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_DEGREES,
 | 
			
		||||
    UNIT_EMPTY,
 | 
			
		||||
@@ -88,24 +88,36 @@ CONFIG_SCHEMA = (
 | 
			
		||||
                STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema(
 | 
			
		||||
                UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE
 | 
			
		||||
                UNIT_WATT_HOURS,
 | 
			
		||||
                ICON_EMPTY,
 | 
			
		||||
                2,
 | 
			
		||||
                DEVICE_CLASS_ENERGY,
 | 
			
		||||
                STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                LAST_RESET_TYPE_AUTO,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema(
 | 
			
		||||
                UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE
 | 
			
		||||
                UNIT_WATT_HOURS,
 | 
			
		||||
                ICON_EMPTY,
 | 
			
		||||
                2,
 | 
			
		||||
                DEVICE_CLASS_ENERGY,
 | 
			
		||||
                STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                LAST_RESET_TYPE_AUTO,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema(
 | 
			
		||||
                UNIT_VOLT_AMPS_REACTIVE_HOURS,
 | 
			
		||||
                ICON_EMPTY,
 | 
			
		||||
                2,
 | 
			
		||||
                DEVICE_CLASS_ENERGY,
 | 
			
		||||
                STATE_CLASS_NONE,
 | 
			
		||||
                STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                LAST_RESET_TYPE_AUTO,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema(
 | 
			
		||||
                UNIT_VOLT_AMPS_REACTIVE_HOURS,
 | 
			
		||||
                ICON_EMPTY,
 | 
			
		||||
                2,
 | 
			
		||||
                DEVICE_CLASS_ENERGY,
 | 
			
		||||
                STATE_CLASS_NONE,
 | 
			
		||||
                STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                LAST_RESET_TYPE_AUTO,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INTERNAL,
 | 
			
		||||
    CONF_LAST_RESET_TYPE,
 | 
			
		||||
    CONF_ON_RAW_VALUE,
 | 
			
		||||
    CONF_ON_VALUE,
 | 
			
		||||
    CONF_ON_VALUE_RANGE,
 | 
			
		||||
@@ -30,6 +31,9 @@ from esphome.const import (
 | 
			
		||||
    CONF_NAME,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_FORCE_UPDATE,
 | 
			
		||||
    LAST_RESET_TYPE_AUTO,
 | 
			
		||||
    LAST_RESET_TYPE_NEVER,
 | 
			
		||||
    LAST_RESET_TYPE_NONE,
 | 
			
		||||
    UNIT_EMPTY,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
@@ -79,6 +83,15 @@ STATE_CLASSES = {
 | 
			
		||||
}
 | 
			
		||||
validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_")
 | 
			
		||||
 | 
			
		||||
LastResetTypes = sensor_ns.enum("LastResetType")
 | 
			
		||||
LAST_RESET_TYPES = {
 | 
			
		||||
    LAST_RESET_TYPE_NONE: LastResetTypes.LAST_RESET_TYPE_NONE,
 | 
			
		||||
    LAST_RESET_TYPE_NEVER: LastResetTypes.LAST_RESET_TYPE_NEVER,
 | 
			
		||||
    LAST_RESET_TYPE_AUTO: LastResetTypes.LAST_RESET_TYPE_AUTO,
 | 
			
		||||
}
 | 
			
		||||
validate_last_reset_type = cv.enum(LAST_RESET_TYPES, lower=True, space="_")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -168,6 +181,7 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
 | 
			
		||||
        cv.Optional(CONF_ACCURACY_DECIMALS): accuracy_decimals,
 | 
			
		||||
        cv.Optional(CONF_DEVICE_CLASS): device_class,
 | 
			
		||||
        cv.Optional(CONF_STATE_CLASS): validate_state_class,
 | 
			
		||||
        cv.Optional(CONF_LAST_RESET_TYPE): validate_last_reset_type,
 | 
			
		||||
        cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_EXPIRE_AFTER): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"),
 | 
			
		||||
@@ -202,6 +216,7 @@ def sensor_schema(
 | 
			
		||||
    accuracy_decimals_: int,
 | 
			
		||||
    device_class_: Optional[str] = DEVICE_CLASS_EMPTY,
 | 
			
		||||
    state_class_: Optional[str] = STATE_CLASS_NONE,
 | 
			
		||||
    last_reset_type_: Optional[str] = LAST_RESET_TYPE_NONE,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = SENSOR_SCHEMA
 | 
			
		||||
    if unit_of_measurement_ != UNIT_EMPTY:
 | 
			
		||||
@@ -230,6 +245,14 @@ def sensor_schema(
 | 
			
		||||
        schema = schema.extend(
 | 
			
		||||
            {cv.Optional(CONF_STATE_CLASS, default=state_class_): validate_state_class}
 | 
			
		||||
        )
 | 
			
		||||
    if last_reset_type_ != LAST_RESET_TYPE_NONE:
 | 
			
		||||
        schema = schema.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_LAST_RESET_TYPE, default=last_reset_type_
 | 
			
		||||
                ): validate_last_reset_type
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    return schema
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -479,6 +502,8 @@ async def setup_sensor_core_(var, config):
 | 
			
		||||
        cg.add(var.set_icon(config[CONF_ICON]))
 | 
			
		||||
    if CONF_ACCURACY_DECIMALS in config:
 | 
			
		||||
        cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS]))
 | 
			
		||||
    if CONF_LAST_RESET_TYPE in config:
 | 
			
		||||
        cg.add(var.set_last_reset_type(config[CONF_LAST_RESET_TYPE]))
 | 
			
		||||
    cg.add(var.set_force_update(config[CONF_FORCE_UPDATE]))
 | 
			
		||||
    if config.get(CONF_FILTERS):  # must exist and not be empty
 | 
			
		||||
        filters = await build_filters(config[CONF_FILTERS])
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,18 @@ const char *state_class_to_string(StateClass state_class) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const char *last_reset_type_to_string(LastResetType last_reset_type) {
 | 
			
		||||
  switch (last_reset_type) {
 | 
			
		||||
    case LAST_RESET_TYPE_NEVER:
 | 
			
		||||
      return "never";
 | 
			
		||||
    case LAST_RESET_TYPE_AUTO:
 | 
			
		||||
      return "auto";
 | 
			
		||||
    case LAST_RESET_TYPE_NONE:
 | 
			
		||||
    default:
 | 
			
		||||
      return "";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Sensor::publish_state(float state) {
 | 
			
		||||
  this->raw_state = state;
 | 
			
		||||
  this->raw_callback_.call(state);
 | 
			
		||||
@@ -64,6 +76,7 @@ void Sensor::set_state_class(const std::string &state_class) {
 | 
			
		||||
    ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void Sensor::set_last_reset_type(LastResetType last_reset_type) { this->last_reset_type = last_reset_type; }
 | 
			
		||||
std::string Sensor::get_unit_of_measurement() {
 | 
			
		||||
  if (this->unit_of_measurement_.has_value())
 | 
			
		||||
    return *this->unit_of_measurement_;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,10 @@ namespace sensor {
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "%s  Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
 | 
			
		||||
    } \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s  State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \
 | 
			
		||||
    if ((obj)->state_class == sensor::STATE_CLASS_MEASUREMENT && \
 | 
			
		||||
        (obj)->last_reset_type != sensor::LAST_RESET_TYPE_NONE) { \
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "%s  Last Reset Type: '%s'", prefix, last_reset_type_to_string((obj)->last_reset_type)); \
 | 
			
		||||
    } \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s  Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s  Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \
 | 
			
		||||
    if (!(obj)->get_icon().empty()) { \
 | 
			
		||||
@@ -37,6 +41,20 @@ enum StateClass : uint8_t {
 | 
			
		||||
 | 
			
		||||
const char *state_class_to_string(StateClass state_class);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sensor last reset types
 | 
			
		||||
 */
 | 
			
		||||
enum LastResetType : uint8_t {
 | 
			
		||||
  /// This sensor does not support resetting. ie, it is not accumulative
 | 
			
		||||
  LAST_RESET_TYPE_NONE = 0,
 | 
			
		||||
  /// This sensor is expected to never reset its value
 | 
			
		||||
  LAST_RESET_TYPE_NEVER = 1,
 | 
			
		||||
  /// This sensor may reset and Home Assistant will watch for this
 | 
			
		||||
  LAST_RESET_TYPE_AUTO = 2,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const char *last_reset_type_to_string(LastResetType last_reset_type);
 | 
			
		||||
 | 
			
		||||
/** Base-class for all sensors.
 | 
			
		||||
 *
 | 
			
		||||
 * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy.
 | 
			
		||||
@@ -155,6 +173,12 @@ class Sensor : public Nameable {
 | 
			
		||||
   */
 | 
			
		||||
  virtual std::string device_class();
 | 
			
		||||
 | 
			
		||||
  // The Last reset type of this sensor
 | 
			
		||||
  LastResetType last_reset_type{LAST_RESET_TYPE_NONE};
 | 
			
		||||
 | 
			
		||||
  /// Manually set the Home Assistant last reset type for this sensor.
 | 
			
		||||
  void set_last_reset_type(LastResetType last_reset_type);
 | 
			
		||||
 | 
			
		||||
  /** A unique ID for this sensor, empty for no unique id. See unique ID requirements:
 | 
			
		||||
   * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements
 | 
			
		||||
   *
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,12 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import number
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INITIAL_VALUE,
 | 
			
		||||
    CONF_LAMBDA,
 | 
			
		||||
    CONF_MAX_VALUE,
 | 
			
		||||
    CONF_MIN_VALUE,
 | 
			
		||||
    CONF_OPTIMISTIC,
 | 
			
		||||
    CONF_RESTORE_VALUE,
 | 
			
		||||
    CONF_STEP,
 | 
			
		||||
)
 | 
			
		||||
from .. import template_ns
 | 
			
		||||
@@ -25,6 +27,17 @@ def validate_min_max(config):
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate(config):
 | 
			
		||||
    if CONF_LAMBDA in config:
 | 
			
		||||
        if CONF_OPTIMISTIC in config:
 | 
			
		||||
            raise cv.Invalid("optimistic cannot be used with lambda")
 | 
			
		||||
        if CONF_INITIAL_VALUE in config:
 | 
			
		||||
            raise cv.Invalid("initial_value cannot be used with lambda")
 | 
			
		||||
        if CONF_RESTORE_VALUE in config:
 | 
			
		||||
            raise cv.Invalid("restore_value cannot be used with lambda")
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    number.NUMBER_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
@@ -33,31 +46,43 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.Required(CONF_MIN_VALUE): cv.float_,
 | 
			
		||||
            cv.Required(CONF_STEP): cv.positive_float,
 | 
			
		||||
            cv.Optional(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
            cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_OPTIMISTIC): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
            cv.Optional(CONF_INITIAL_VALUE): cv.float_,
 | 
			
		||||
            cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.polling_component_schema("60s")),
 | 
			
		||||
    validate_min_max,
 | 
			
		||||
    validate,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await number.register_number(var, config)
 | 
			
		||||
    await number.register_number(
 | 
			
		||||
        var,
 | 
			
		||||
        config,
 | 
			
		||||
        min_value=config[CONF_MIN_VALUE],
 | 
			
		||||
        max_value=config[CONF_MAX_VALUE],
 | 
			
		||||
        step=config[CONF_STEP],
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if CONF_LAMBDA in config:
 | 
			
		||||
        template_ = await cg.process_lambda(
 | 
			
		||||
            config[CONF_LAMBDA], [], return_type=cg.optional.template(float)
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_template(template_))
 | 
			
		||||
 | 
			
		||||
    else:
 | 
			
		||||
        if CONF_OPTIMISTIC in config:
 | 
			
		||||
            cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
 | 
			
		||||
        if CONF_INITIAL_VALUE in config:
 | 
			
		||||
            cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE]))
 | 
			
		||||
        if CONF_RESTORE_VALUE in config:
 | 
			
		||||
            cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
 | 
			
		||||
 | 
			
		||||
    if CONF_SET_ACTION in config:
 | 
			
		||||
        await automation.build_automation(
 | 
			
		||||
            var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_min_value(config[CONF_MIN_VALUE]))
 | 
			
		||||
    cg.add(var.set_max_value(config[CONF_MAX_VALUE]))
 | 
			
		||||
    cg.add(var.set_step(config[CONF_STEP]))
 | 
			
		||||
 
 | 
			
		||||
@@ -6,34 +6,50 @@ namespace template_ {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "template.number";
 | 
			
		||||
 | 
			
		||||
TemplateNumber::TemplateNumber() : set_trigger_(new Trigger<float>()) {}
 | 
			
		||||
void TemplateNumber::setup() {
 | 
			
		||||
  if (this->f_.has_value())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  float value;
 | 
			
		||||
  if (!this->restore_value_) {
 | 
			
		||||
    value = this->initial_value_;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->pref_ = global_preferences.make_preference<float>(this->get_object_id_hash());
 | 
			
		||||
    if (!this->pref_.load(&value)) {
 | 
			
		||||
      if (!isnan(this->initial_value_))
 | 
			
		||||
        value = this->initial_value_;
 | 
			
		||||
      else
 | 
			
		||||
        value = this->traits.get_min_value();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  this->publish_state(value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TemplateNumber::update() {
 | 
			
		||||
  if (!this->f_.has_value())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  auto val = (*this->f_)();
 | 
			
		||||
  if (val.has_value()) {
 | 
			
		||||
    this->publish_state(*val);
 | 
			
		||||
  }
 | 
			
		||||
  if (!val.has_value())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  this->publish_state(*val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TemplateNumber::set(float value) {
 | 
			
		||||
void TemplateNumber::control(float value) {
 | 
			
		||||
  this->set_trigger_->trigger(value);
 | 
			
		||||
 | 
			
		||||
  if (this->optimistic_)
 | 
			
		||||
    this->publish_state(value);
 | 
			
		||||
 | 
			
		||||
  if (this->restore_value_)
 | 
			
		||||
    this->pref_.save(&value);
 | 
			
		||||
}
 | 
			
		||||
float TemplateNumber::get_setup_priority() const { return setup_priority::HARDWARE; }
 | 
			
		||||
void TemplateNumber::set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
 | 
			
		||||
void TemplateNumber::dump_config() {
 | 
			
		||||
  LOG_NUMBER("", "Template Number", this);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Optimistic: %s", YESNO(this->optimistic_));
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TemplateNumber::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
 | 
			
		||||
Trigger<float> *TemplateNumber::get_set_trigger() const { return this->set_trigger_; };
 | 
			
		||||
 | 
			
		||||
}  // namespace template_
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -3,27 +3,34 @@
 | 
			
		||||
#include "esphome/components/number/number.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/preferences.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace template_ {
 | 
			
		||||
 | 
			
		||||
class TemplateNumber : public number::Number, public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  TemplateNumber();
 | 
			
		||||
  void set_template(std::function<optional<float>()> &&f);
 | 
			
		||||
  void set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::HARDWARE; }
 | 
			
		||||
 | 
			
		||||
  Trigger<float> *get_set_trigger() const;
 | 
			
		||||
  void set_optimistic(bool optimistic);
 | 
			
		||||
  Trigger<float> *get_set_trigger() const { return set_trigger_; }
 | 
			
		||||
  void set_optimistic(bool optimistic) { optimistic_ = optimistic; }
 | 
			
		||||
  void set_initial_value(float initial_value) { initial_value_ = initial_value; }
 | 
			
		||||
  void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void set(float value) override;
 | 
			
		||||
  void control(float value) override;
 | 
			
		||||
  bool optimistic_{false};
 | 
			
		||||
  Trigger<float> *set_trigger_;
 | 
			
		||||
  float initial_value_{NAN};
 | 
			
		||||
  bool restore_value_{false};
 | 
			
		||||
  Trigger<float> *set_trigger_ = new Trigger<float>();
 | 
			
		||||
  optional<std::function<optional<float>()>> f_;
 | 
			
		||||
 | 
			
		||||
  ESPPreferenceObject pref_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace template_
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,10 @@ void CronTrigger::loop() {
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (this->last_check_.has_value()) {
 | 
			
		||||
    if (*this->last_check_ >= time) {
 | 
			
		||||
    if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > 900) {
 | 
			
		||||
      // We went back in time (a lot), probably caused by time synchronization
 | 
			
		||||
      ESP_LOGW(TAG, "Time has jumped back!");
 | 
			
		||||
    } else if (*this->last_check_ >= time) {
 | 
			
		||||
      // already handled this one
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,15 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, time
 | 
			
		||||
from esphome.const import CONF_ID, CONF_TIME_ID
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_TIME_ID,
 | 
			
		||||
    DEVICE_CLASS_ENERGY,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    LAST_RESET_TYPE_AUTO,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_EMPTY,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["time"]
 | 
			
		||||
 | 
			
		||||
@@ -11,13 +19,24 @@ TotalDailyEnergy = total_daily_energy_ns.class_(
 | 
			
		||||
    "TotalDailyEnergy", sensor.Sensor, cg.Component
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(TotalDailyEnergy),
 | 
			
		||||
        cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
 | 
			
		||||
        cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    sensor.sensor_schema(
 | 
			
		||||
        UNIT_EMPTY,
 | 
			
		||||
        ICON_EMPTY,
 | 
			
		||||
        0,
 | 
			
		||||
        DEVICE_CLASS_ENERGY,
 | 
			
		||||
        STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        LAST_RESET_TYPE_AUTO,
 | 
			
		||||
    )
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(TotalDailyEnergy),
 | 
			
		||||
            cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
 | 
			
		||||
            cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,11 @@ namespace esphome {
 | 
			
		||||
namespace tuya {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "tuya";
 | 
			
		||||
static const int COMMAND_DELAY = 50;
 | 
			
		||||
static const int COMMAND_DELAY = 10;
 | 
			
		||||
static const int RECEIVE_TIMEOUT = 300;
 | 
			
		||||
 | 
			
		||||
void Tuya::setup() {
 | 
			
		||||
  this->set_interval("heartbeat", 10000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
 | 
			
		||||
  this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Tuya::loop() {
 | 
			
		||||
@@ -113,11 +114,19 @@ void Tuya::handle_char_(uint8_t c) {
 | 
			
		||||
  this->rx_message_.push_back(c);
 | 
			
		||||
  if (!this->validate_message_()) {
 | 
			
		||||
    this->rx_message_.clear();
 | 
			
		||||
  } else {
 | 
			
		||||
    this->last_rx_char_timestamp_ = millis();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) {
 | 
			
		||||
  switch ((TuyaCommandType) command) {
 | 
			
		||||
  TuyaCommandType command_type = (TuyaCommandType) command;
 | 
			
		||||
 | 
			
		||||
  if (this->expected_response_.has_value() && this->expected_response_ == command_type) {
 | 
			
		||||
    this->expected_response_.reset();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (command_type) {
 | 
			
		||||
    case TuyaCommandType::HEARTBEAT:
 | 
			
		||||
      ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]);
 | 
			
		||||
      this->protocol_version_ = version;
 | 
			
		||||
@@ -316,6 +325,25 @@ void Tuya::send_raw_command_(TuyaCommand command) {
 | 
			
		||||
  uint8_t version = 0;
 | 
			
		||||
 | 
			
		||||
  this->last_command_timestamp_ = millis();
 | 
			
		||||
  switch (command.cmd) {
 | 
			
		||||
    case TuyaCommandType::HEARTBEAT:
 | 
			
		||||
      this->expected_response_ = TuyaCommandType::HEARTBEAT;
 | 
			
		||||
      break;
 | 
			
		||||
    case TuyaCommandType::PRODUCT_QUERY:
 | 
			
		||||
      this->expected_response_ = TuyaCommandType::PRODUCT_QUERY;
 | 
			
		||||
      break;
 | 
			
		||||
    case TuyaCommandType::CONF_QUERY:
 | 
			
		||||
      this->expected_response_ = TuyaCommandType::CONF_QUERY;
 | 
			
		||||
      break;
 | 
			
		||||
    case TuyaCommandType::DATAPOINT_DELIVER:
 | 
			
		||||
      this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT;
 | 
			
		||||
      break;
 | 
			
		||||
    case TuyaCommandType::DATAPOINT_QUERY:
 | 
			
		||||
      this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast<uint8_t>(command.cmd),
 | 
			
		||||
           version, hexencode(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
 | 
			
		||||
@@ -331,9 +359,20 @@ void Tuya::send_raw_command_(TuyaCommand command) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Tuya::process_command_queue_() {
 | 
			
		||||
  uint32_t delay = millis() - this->last_command_timestamp_;
 | 
			
		||||
  uint32_t now = millis();
 | 
			
		||||
  uint32_t delay = now - this->last_command_timestamp_;
 | 
			
		||||
 | 
			
		||||
  if (now - this->last_rx_char_timestamp_ > RECEIVE_TIMEOUT) {
 | 
			
		||||
    this->rx_message_.clear();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) {
 | 
			
		||||
    this->expected_response_.reset();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly
 | 
			
		||||
  if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) {
 | 
			
		||||
  if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() &&
 | 
			
		||||
      !this->expected_response_.has_value()) {
 | 
			
		||||
    this->send_raw_command_(command_queue_.front());
 | 
			
		||||
    this->command_queue_.erase(command_queue_.begin());
 | 
			
		||||
  }
 | 
			
		||||
@@ -345,7 +384,7 @@ void Tuya::send_command_(const TuyaCommand &command) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Tuya::send_empty_command_(TuyaCommandType command) {
 | 
			
		||||
  send_command_(TuyaCommand{.cmd = command, .payload = std::vector<uint8_t>{0x04}});
 | 
			
		||||
  send_command_(TuyaCommand{.cmd = command, .payload = std::vector<uint8_t>{}});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Tuya::send_wifi_status_() {
 | 
			
		||||
 
 | 
			
		||||
@@ -107,12 +107,14 @@ class Tuya : public Component, public uart::UARTDevice {
 | 
			
		||||
  int gpio_status_ = -1;
 | 
			
		||||
  int gpio_reset_ = -1;
 | 
			
		||||
  uint32_t last_command_timestamp_ = 0;
 | 
			
		||||
  uint32_t last_rx_char_timestamp_ = 0;
 | 
			
		||||
  std::string product_ = "";
 | 
			
		||||
  std::vector<TuyaDatapointListener> listeners_;
 | 
			
		||||
  std::vector<TuyaDatapoint> datapoints_;
 | 
			
		||||
  std::vector<uint8_t> rx_message_;
 | 
			
		||||
  std::vector<uint8_t> ignore_mcu_update_on_datapoints_{};
 | 
			
		||||
  std::vector<TuyaCommand> command_queue_;
 | 
			
		||||
  optional<TuyaCommandType> expected_response_{};
 | 
			
		||||
  uint8_t wifi_status_ = -1;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,7 @@ class ESP8266SoftwareSerial {
 | 
			
		||||
class UARTComponent : public Component, public Stream {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; }
 | 
			
		||||
  uint32_t get_baud_rate() const { return baud_rate_; }
 | 
			
		||||
 | 
			
		||||
  uint32_t get_config();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -614,8 +614,9 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
 | 
			
		||||
std::string WebServer::number_json(number::Number *obj, float value) {
 | 
			
		||||
  return json::build_json([obj, value](JsonObject &root) {
 | 
			
		||||
    root["id"] = "number-" + obj->get_object_id();
 | 
			
		||||
    std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals());
 | 
			
		||||
    root["state"] = state;
 | 
			
		||||
    char buffer[64];
 | 
			
		||||
    snprintf(buffer, sizeof(buffer), "%f", value);
 | 
			
		||||
    root["state"] = buffer;
 | 
			
		||||
    root["value"] = value;
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,6 @@
 | 
			
		||||
"""Constants used by esphome."""
 | 
			
		||||
 | 
			
		||||
MAJOR_VERSION = 1
 | 
			
		||||
MINOR_VERSION = 20
 | 
			
		||||
PATCH_VERSION = "0b1"
 | 
			
		||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
 | 
			
		||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
 | 
			
		||||
__version__ = "1.20.4"
 | 
			
		||||
 | 
			
		||||
ESP_PLATFORM_ESP32 = "ESP32"
 | 
			
		||||
ESP_PLATFORM_ESP8266 = "ESP8266"
 | 
			
		||||
@@ -301,6 +297,7 @@ CONF_KEY = "key"
 | 
			
		||||
CONF_LAMBDA = "lambda"
 | 
			
		||||
CONF_LAST_CONFIDENCE = "last_confidence"
 | 
			
		||||
CONF_LAST_FINGER_ID = "last_finger_id"
 | 
			
		||||
CONF_LAST_RESET_TYPE = "last_reset_type"
 | 
			
		||||
CONF_LATITUDE = "latitude"
 | 
			
		||||
CONF_LENGTH = "length"
 | 
			
		||||
CONF_LEVEL = "level"
 | 
			
		||||
@@ -789,3 +786,10 @@ STATE_CLASS_NONE = ""
 | 
			
		||||
 | 
			
		||||
# The state represents a measurement in present time
 | 
			
		||||
STATE_CLASS_MEASUREMENT = "measurement"
 | 
			
		||||
 | 
			
		||||
# This sensor does not support resetting. ie, it is not accumulative
 | 
			
		||||
LAST_RESET_TYPE_NONE = ""
 | 
			
		||||
# This sensor is expected to never reset its value
 | 
			
		||||
LAST_RESET_TYPE_NEVER = "never"
 | 
			
		||||
# This sensor may reset and Home Assistant will watch for this
 | 
			
		||||
LAST_RESET_TYPE_AUTO = "auto"
 | 
			
		||||
 
 | 
			
		||||
@@ -782,10 +782,9 @@ def make_app(debug=get_bool_env(ENV_DEV)):
 | 
			
		||||
 | 
			
		||||
    class StaticFileHandler(tornado.web.StaticFileHandler):
 | 
			
		||||
        def set_extra_headers(self, path):
 | 
			
		||||
            if debug:
 | 
			
		||||
                self.set_header(
 | 
			
		||||
                    "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"
 | 
			
		||||
                )
 | 
			
		||||
            self.set_header(
 | 
			
		||||
                "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    app_settings = {
 | 
			
		||||
        "debug": debug,
 | 
			
		||||
 
 | 
			
		||||
@@ -1104,7 +1104,7 @@ def shorthand_input_pullup_pin(value):
 | 
			
		||||
 | 
			
		||||
def shorthand_analog_pin(value):
 | 
			
		||||
    value = analog_pin(value)
 | 
			
		||||
    return GPIO_FULL_INPUT_PIN_SCHEMA({CONF_NUMBER: value})
 | 
			
		||||
    return GPIO_FULL_ANALOG_PIN_SCHEMA({CONF_NUMBER: value})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_has_interrupt(value):
 | 
			
		||||
 
 | 
			
		||||
@@ -11,4 +11,4 @@ ifaddr==0.1.7
 | 
			
		||||
platformio==5.1.1
 | 
			
		||||
esptool==2.8
 | 
			
		||||
click==7.1.2
 | 
			
		||||
esphome-dashboard==20210623.0
 | 
			
		||||
esphome-dashboard==20210728.0
 | 
			
		||||
 
 | 
			
		||||
@@ -1,50 +0,0 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
import argparse
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def sub(path, pattern, repl, expected_count=1):
 | 
			
		||||
    with open(path) as fh:
 | 
			
		||||
        content = fh.read()
 | 
			
		||||
    content, count = re.subn(pattern, repl, content, flags=re.MULTILINE)
 | 
			
		||||
    if expected_count is not None:
 | 
			
		||||
        assert count == expected_count, f"Pattern {pattern} replacement failed!"
 | 
			
		||||
    with open(path, "wt") as fh:
 | 
			
		||||
        fh.write(content)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def write_version(version: str):
 | 
			
		||||
    for p in [
 | 
			
		||||
        ".github/workflows/ci-docker.yml",
 | 
			
		||||
        ".github/workflows/release-dev.yml",
 | 
			
		||||
        ".github/workflows/release.yml",
 | 
			
		||||
    ]:
 | 
			
		||||
        sub(p, r'base_version=".*"', f'base_version="{version}"')
 | 
			
		||||
 | 
			
		||||
    sub(
 | 
			
		||||
        "docker/Dockerfile",
 | 
			
		||||
        r"ARG BUILD_FROM=esphome/esphome-base-amd64:.*",
 | 
			
		||||
        f"ARG BUILD_FROM=esphome/esphome-base-amd64:{version}",
 | 
			
		||||
    )
 | 
			
		||||
    sub(
 | 
			
		||||
        "docker/Dockerfile.lint",
 | 
			
		||||
        r"FROM esphome/esphome-lint-base:.*",
 | 
			
		||||
        f"FROM esphome/esphome-lint-base:{version}",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    parser = argparse.ArgumentParser()
 | 
			
		||||
    parser.add_argument("new_version", type=str)
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    version = args.new_version
 | 
			
		||||
    print(f"Bumping to {version}")
 | 
			
		||||
    write_version(version)
 | 
			
		||||
    return 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    sys.exit(main() or 0)
 | 
			
		||||
@@ -50,16 +50,10 @@ def sub(path, pattern, repl, expected_count=1):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def write_version(version: Version):
 | 
			
		||||
    sub(
 | 
			
		||||
        "esphome/const.py", r"^MAJOR_VERSION = \d+$", f"MAJOR_VERSION = {version.major}"
 | 
			
		||||
    )
 | 
			
		||||
    sub(
 | 
			
		||||
        "esphome/const.py", r"^MINOR_VERSION = \d+$", f"MINOR_VERSION = {version.minor}"
 | 
			
		||||
    )
 | 
			
		||||
    sub(
 | 
			
		||||
        "esphome/const.py",
 | 
			
		||||
        r"^PATCH_VERSION = .*$",
 | 
			
		||||
        f'PATCH_VERSION = "{version.full_patch}"',
 | 
			
		||||
        r"^__version__ = .*$",
 | 
			
		||||
        f'__version__ = "{version}"',
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
from helpers import git_ls_files, filter_changed
 | 
			
		||||
import codecs
 | 
			
		||||
import collections
 | 
			
		||||
import fnmatch
 | 
			
		||||
@@ -12,7 +13,6 @@ import functools
 | 
			
		||||
import argparse
 | 
			
		||||
 | 
			
		||||
sys.path.append(os.path.dirname(__file__))
 | 
			
		||||
from helpers import git_ls_files, filter_changed
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def find_all(a_str, sub):
 | 
			
		||||
@@ -562,6 +562,7 @@ def lint_inclusive_language(fname, match):
 | 
			
		||||
        "esphome/components/number/number.h",
 | 
			
		||||
        "esphome/components/output/binary_output.h",
 | 
			
		||||
        "esphome/components/output/float_output.h",
 | 
			
		||||
        "esphome/components/nextion/nextion_base.h",
 | 
			
		||||
        "esphome/components/sensor/sensor.h",
 | 
			
		||||
        "esphome/components/stepper/stepper.h",
 | 
			
		||||
        "esphome/components/switch/switch.h",
 | 
			
		||||
 
 | 
			
		||||
@@ -1055,10 +1055,6 @@ binary_sensor:
 | 
			
		||||
    pin: GPIO27
 | 
			
		||||
    threshold: 1000
 | 
			
		||||
    id: btn_left
 | 
			
		||||
  - platform: nextion
 | 
			
		||||
    page_id: 0
 | 
			
		||||
    component_id: 2
 | 
			
		||||
    name: 'Nextion Component 2 Touch'
 | 
			
		||||
  - platform: template
 | 
			
		||||
    name: 'Garage Door Open'
 | 
			
		||||
    id: garage_door
 | 
			
		||||
@@ -1882,11 +1878,6 @@ display:
 | 
			
		||||
    intensity: 3
 | 
			
		||||
    lambda: |-
 | 
			
		||||
      it.print("1234");
 | 
			
		||||
  - platform: nextion
 | 
			
		||||
    uart_id: uart0
 | 
			
		||||
    lambda: |-
 | 
			
		||||
      it.set_component_value("gauge", 50);
 | 
			
		||||
      it.set_component_text("textview", "Hello World!");
 | 
			
		||||
  - platform: pcd8544
 | 
			
		||||
    cs_pin: GPIO23
 | 
			
		||||
    dc_pin: GPIO23
 | 
			
		||||
 
 | 
			
		||||
@@ -269,6 +269,7 @@ wled:
 | 
			
		||||
 | 
			
		||||
adalight:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sensor:
 | 
			
		||||
  - platform: apds9960
 | 
			
		||||
    type: proximity
 | 
			
		||||
@@ -534,6 +535,15 @@ sensor:
 | 
			
		||||
    export_reactive_energy:
 | 
			
		||||
      name: 'Export Reactive Energy'
 | 
			
		||||
 | 
			
		||||
  - platform: nextion
 | 
			
		||||
    id: testnumber
 | 
			
		||||
    name: 'testnumber'
 | 
			
		||||
    variable_name: testnumber
 | 
			
		||||
  - platform: nextion
 | 
			
		||||
    id: testwave
 | 
			
		||||
    name: 'testwave'
 | 
			
		||||
    component_id: 2
 | 
			
		||||
    wave_channel_id: 1
 | 
			
		||||
time:
 | 
			
		||||
  - platform: homeassistant
 | 
			
		||||
 | 
			
		||||
@@ -605,7 +615,14 @@ binary_sensor:
 | 
			
		||||
    binary_sensors:
 | 
			
		||||
      - id: custom_binary_sensor
 | 
			
		||||
        name: Custom Binary Sensor
 | 
			
		||||
 | 
			
		||||
  - platform: nextion
 | 
			
		||||
    page_id: 0
 | 
			
		||||
    component_id: 2
 | 
			
		||||
    name: 'Nextion Component 2 Touch'
 | 
			
		||||
  - platform: nextion
 | 
			
		||||
    id: r0_sensor
 | 
			
		||||
    name: 'R0 Sensor'
 | 
			
		||||
    component_name: page0.r0
 | 
			
		||||
globals:
 | 
			
		||||
  - id: my_global_string
 | 
			
		||||
    type: std::string
 | 
			
		||||
@@ -653,6 +670,11 @@ text_sensor:
 | 
			
		||||
    text_sensors:
 | 
			
		||||
      - id: custom_text_sensor
 | 
			
		||||
        name: Custom Text Sensor
 | 
			
		||||
  - platform: nextion
 | 
			
		||||
    name: text0
 | 
			
		||||
    id: text0
 | 
			
		||||
    update_interval: 4s
 | 
			
		||||
    component_name: text0
 | 
			
		||||
 | 
			
		||||
script:
 | 
			
		||||
  - id: my_script
 | 
			
		||||
@@ -704,6 +726,10 @@ switch:
 | 
			
		||||
    switches:
 | 
			
		||||
      - id: custom_switch
 | 
			
		||||
        name: Custom Switch
 | 
			
		||||
  - platform: nextion
 | 
			
		||||
    id: r0
 | 
			
		||||
    name: 'R0 Switch'
 | 
			
		||||
    component_name: page0.r0
 | 
			
		||||
 | 
			
		||||
custom_component:
 | 
			
		||||
  lambda: |-
 | 
			
		||||
@@ -1086,6 +1112,16 @@ display:
 | 
			
		||||
    id: my_matrix
 | 
			
		||||
    lambda: |-
 | 
			
		||||
      it.printdigit("hello");
 | 
			
		||||
  - platform: nextion
 | 
			
		||||
    uart_id: uart1
 | 
			
		||||
    tft_url: 'http://esphome.io/default35.tft'
 | 
			
		||||
    update_interval: 5s
 | 
			
		||||
    on_sleep:
 | 
			
		||||
      then:
 | 
			
		||||
        lambda: 'ESP_LOGD("display","Display went to sleep");'
 | 
			
		||||
    on_wake:
 | 
			
		||||
      then:
 | 
			
		||||
        lambda: 'ESP_LOGD("display","Display woke up");'
 | 
			
		||||
 | 
			
		||||
http_request:
 | 
			
		||||
  useragent: esphome/device
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user