mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 16:41:50 +00:00 
			
		
		
		
	Compare commits
	
		
			13 Commits
		
	
	
		
			2023.7.1
			...
			jesserockz
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					fcf0761ba3 | ||
| 
						 | 
					4b9732629e | ||
| 
						 | 
					5cef11acf0 | ||
| 
						 | 
					b1ad48c824 | ||
| 
						 | 
					35b1b5fae6 | ||
| 
						 | 
					ce20b64712 | ||
| 
						 | 
					1720b2a37e | ||
| 
						 | 
					c974fbbea8 | ||
| 
						 | 
					00c5233bf4 | ||
| 
						 | 
					7efb138933 | ||
| 
						 | 
					f73435f820 | ||
| 
						 | 
					bfc55beedc | ||
| 
						 | 
					9fa057eae8 | 
@@ -40,7 +40,6 @@
 | 
			
		||||
        "yaml.customTags": [
 | 
			
		||||
          "!secret scalar",
 | 
			
		||||
          "!lambda scalar",
 | 
			
		||||
          "!extend scalar",
 | 
			
		||||
          "!include_dir_named scalar",
 | 
			
		||||
          "!include_dir_list scalar",
 | 
			
		||||
          "!include_dir_merge_list scalar",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,2 @@
 | 
			
		||||
# Normalize line endings to LF in the repository
 | 
			
		||||
* text eol=lf
 | 
			
		||||
*.png binary
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										38
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,38 +0,0 @@
 | 
			
		||||
name: Restore Python
 | 
			
		||||
inputs:
 | 
			
		||||
  python-version:
 | 
			
		||||
    description: Python version to restore
 | 
			
		||||
    required: true
 | 
			
		||||
    type: string
 | 
			
		||||
  cache-key:
 | 
			
		||||
    description: Cache key to use
 | 
			
		||||
    required: true
 | 
			
		||||
    type: string
 | 
			
		||||
outputs:
 | 
			
		||||
  python-version:
 | 
			
		||||
    description: Python version restored
 | 
			
		||||
    value: ${{ steps.python.outputs.python-version }}
 | 
			
		||||
runs:
 | 
			
		||||
  using: "composite"
 | 
			
		||||
  steps:
 | 
			
		||||
    - name: Set up Python ${{ inputs.python-version }}
 | 
			
		||||
      id: python
 | 
			
		||||
      uses: actions/setup-python@v4.6.0
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: ${{ inputs.python-version }}
 | 
			
		||||
    - name: Restore Python virtual environment
 | 
			
		||||
      id: cache-venv
 | 
			
		||||
      uses: actions/cache/restore@v3.3.1
 | 
			
		||||
      with:
 | 
			
		||||
        path: venv
 | 
			
		||||
        # yamllint disable-line rule:line-length
 | 
			
		||||
        key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ inputs.cache-key }}
 | 
			
		||||
    - name: Create Python virtual environment
 | 
			
		||||
      if: steps.cache-venv.outputs.cache-hit != 'true'
 | 
			
		||||
      shell: bash
 | 
			
		||||
      run: |
 | 
			
		||||
        python -m venv venv
 | 
			
		||||
        . venv/bin/activate
 | 
			
		||||
        python --version
 | 
			
		||||
        pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
			
		||||
        pip install -e .
 | 
			
		||||
							
								
								
									
										392
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										392
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -12,258 +12,60 @@ on:
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
 | 
			
		||||
env:
 | 
			
		||||
  DEFAULT_PYTHON: "3.9"
 | 
			
		||||
  PYUPGRADE_TARGET: "--py39-plus"
 | 
			
		||||
  CLANG_FORMAT_VERSION: "13.0.1"
 | 
			
		||||
 | 
			
		||||
concurrency:
 | 
			
		||||
  # yamllint disable-line rule:line-length
 | 
			
		||||
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
 | 
			
		||||
  cancel-in-progress: true
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  common:
 | 
			
		||||
    name: Create common environment
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    outputs:
 | 
			
		||||
      cache-key: ${{ steps.cache-key.outputs.key }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v3.5.2
 | 
			
		||||
      - name: Generate cache-key
 | 
			
		||||
        id: cache-key
 | 
			
		||||
        run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
 | 
			
		||||
      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
        id: python
 | 
			
		||||
        uses: actions/setup-python@v4.6.0
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
      - name: Restore Python virtual environment
 | 
			
		||||
        id: cache-venv
 | 
			
		||||
        uses: actions/cache@v3.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          path: venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
          key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ steps.cache-key.outputs.key }}
 | 
			
		||||
      - name: Create Python virtual environment
 | 
			
		||||
        if: steps.cache-venv.outputs.cache-hit != 'true'
 | 
			
		||||
        run: |
 | 
			
		||||
          python -m venv venv
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          python --version
 | 
			
		||||
          pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
			
		||||
          pip install -e .
 | 
			
		||||
 | 
			
		||||
  yamllint:
 | 
			
		||||
    name: yamllint
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v3.5.2
 | 
			
		||||
      - name: Run yamllint
 | 
			
		||||
        uses: frenck/action-yamllint@v1.4.1
 | 
			
		||||
 | 
			
		||||
  black:
 | 
			
		||||
    name: Check black
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v3.5.2
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
      - name: Run black
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          black --verbose esphome tests
 | 
			
		||||
      - name: Suggested changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
        if: always()
 | 
			
		||||
 | 
			
		||||
  flake8:
 | 
			
		||||
    name: Check flake8
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v3.5.2
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
      - name: Run flake8
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          flake8 esphome
 | 
			
		||||
      - name: Suggested changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
        if: always()
 | 
			
		||||
 | 
			
		||||
  pylint:
 | 
			
		||||
    name: Check pylint
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v3.5.2
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
      - name: Run pylint
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          pylint -f parseable --persistent=n esphome
 | 
			
		||||
      - name: Suggested changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
        if: always()
 | 
			
		||||
 | 
			
		||||
  pyupgrade:
 | 
			
		||||
    name: Check pyupgrade
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v3.5.2
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
      - name: Run pyupgrade
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          pyupgrade ${{ env.PYUPGRADE_TARGET }} `find esphome -name "*.py" -type f`
 | 
			
		||||
      - name: Suggested changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
        if: always()
 | 
			
		||||
 | 
			
		||||
  ci-custom:
 | 
			
		||||
    name: Run script/ci-custom
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v3.5.2
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
      - name: Register matcher
 | 
			
		||||
        run: echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
			
		||||
      - name: Run script/ci-custom
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          script/ci-custom.py
 | 
			
		||||
          script/build_codeowners.py --check
 | 
			
		||||
 | 
			
		||||
  pytest:
 | 
			
		||||
    name: Run pytest
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v3.5.2
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
      - name: Register matcher
 | 
			
		||||
        run: echo "::add-matcher::.github/workflows/matchers/pytest.json"
 | 
			
		||||
      - name: Run pytest
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          pytest -vv --tb=native tests
 | 
			
		||||
 | 
			
		||||
  clang-format:
 | 
			
		||||
    name: Check clang-format
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v3.5.2
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
      - name: Install clang-format
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          pip install clang-format==${{ env.CLANG_FORMAT_VERSION }}
 | 
			
		||||
      - name: Run clang-format
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          script/clang-format -i
 | 
			
		||||
          git diff-index --quiet HEAD --
 | 
			
		||||
      - name: Suggested changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
        if: always()
 | 
			
		||||
 | 
			
		||||
  compile-tests:
 | 
			
		||||
    name: Run YAML test ${{ matrix.file }}
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
      - black
 | 
			
		||||
      - ci-custom
 | 
			
		||||
      - clang-format
 | 
			
		||||
      - flake8
 | 
			
		||||
      - pylint
 | 
			
		||||
      - pytest
 | 
			
		||||
      - pyupgrade
 | 
			
		||||
      - yamllint
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      max-parallel: 2
 | 
			
		||||
      matrix:
 | 
			
		||||
        file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8]
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v3.5.2
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
      - name: Run esphome compile tests/test${{ matrix.file }}.yaml
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          esphome compile tests/test${{ matrix.file }}.yaml
 | 
			
		||||
 | 
			
		||||
  clang-tidy:
 | 
			
		||||
  ci:
 | 
			
		||||
    name: ${{ matrix.name }}
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
      - black
 | 
			
		||||
      - ci-custom
 | 
			
		||||
      - clang-format
 | 
			
		||||
      - flake8
 | 
			
		||||
      - pylint
 | 
			
		||||
      - pytest
 | 
			
		||||
      - pyupgrade
 | 
			
		||||
      - yamllint
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      max-parallel: 2
 | 
			
		||||
      max-parallel: 5
 | 
			
		||||
      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
 | 
			
		||||
            pio_cache_key: test1
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test2.yaml
 | 
			
		||||
            name: Test tests/test2.yaml
 | 
			
		||||
            pio_cache_key: test2
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test3.yaml
 | 
			
		||||
            name: Test tests/test3.yaml
 | 
			
		||||
            pio_cache_key: test3
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test3.1.yaml
 | 
			
		||||
            name: Test tests/test3.1.yaml
 | 
			
		||||
            pio_cache_key: test3.1
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test4.yaml
 | 
			
		||||
            name: Test tests/test4.yaml
 | 
			
		||||
            pio_cache_key: test4
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test5.yaml
 | 
			
		||||
            name: Test tests/test5.yaml
 | 
			
		||||
            pio_cache_key: test5
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test6.yaml
 | 
			
		||||
            name: Test tests/test6.yaml
 | 
			
		||||
            pio_cache_key: test6
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test7.yaml
 | 
			
		||||
            name: Test tests/test7.yaml
 | 
			
		||||
            pio_cache_key: test7
 | 
			
		||||
          - id: pytest
 | 
			
		||||
            name: Run pytest
 | 
			
		||||
          - id: clang-format
 | 
			
		||||
            name: Run script/clang-format
 | 
			
		||||
          - id: clang-tidy
 | 
			
		||||
            name: Run script/clang-tidy for ESP8266
 | 
			
		||||
            options: --environment esp8266-arduino-tidy --grep USE_ESP8266
 | 
			
		||||
@@ -288,63 +90,119 @@ jobs:
 | 
			
		||||
            name: Run script/clang-tidy for ESP32 IDF
 | 
			
		||||
            options: --environment esp32-idf-tidy --grep USE_ESP_IDF
 | 
			
		||||
            pio_cache_key: tidyesp32-idf
 | 
			
		||||
          - id: yamllint
 | 
			
		||||
            name: Run yamllint
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v3.5.2
 | 
			
		||||
      - name: Restore Python
 | 
			
		||||
        uses: ./.github/actions/restore-python
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v4
 | 
			
		||||
        id: python
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
          python-version: "3.9"
 | 
			
		||||
 | 
			
		||||
      - name: Cache virtualenv
 | 
			
		||||
        uses: actions/cache@v3
 | 
			
		||||
        with:
 | 
			
		||||
          path: .venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
          key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            venv-${{ steps.python.outputs.python-version }}-
 | 
			
		||||
 | 
			
		||||
      - name: Set up virtualenv
 | 
			
		||||
        # yamllint disable rule:line-length
 | 
			
		||||
        run: |
 | 
			
		||||
          python -m venv .venv
 | 
			
		||||
          source .venv/bin/activate
 | 
			
		||||
          pip install -U pip
 | 
			
		||||
          pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
			
		||||
          pip install -e .
 | 
			
		||||
          echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
 | 
			
		||||
          echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
 | 
			
		||||
        # yamllint enable rule:line-length
 | 
			
		||||
 | 
			
		||||
      # Use per check platformio cache because checks use different parts
 | 
			
		||||
      - name: Cache platformio
 | 
			
		||||
        uses: actions/cache@v3.3.1
 | 
			
		||||
        uses: actions/cache@v3
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
          key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
 | 
			
		||||
        if: matrix.id == 'test' || matrix.id == 'clang-tidy'
 | 
			
		||||
 | 
			
		||||
      - name: Install clang-tidy
 | 
			
		||||
        run: sudo apt-get install clang-tidy-11
 | 
			
		||||
      - name: Install clang tools
 | 
			
		||||
        run: |
 | 
			
		||||
          sudo apt-get install \
 | 
			
		||||
              clang-format-13 \
 | 
			
		||||
              clang-tidy-11
 | 
			
		||||
        if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format'
 | 
			
		||||
 | 
			
		||||
      - 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"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/pytest.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-tidy
 | 
			
		||||
      - name: Lint Custom
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          script/clang-tidy --all-headers --fix ${{ matrix.options }}
 | 
			
		||||
          script/ci-custom.py
 | 
			
		||||
          script/build_codeowners.py --check
 | 
			
		||||
        if: matrix.id == 'ci-custom'
 | 
			
		||||
 | 
			
		||||
      - name: Lint Python
 | 
			
		||||
        run: script/lint-python -a
 | 
			
		||||
        if: matrix.id == 'lint-python'
 | 
			
		||||
 | 
			
		||||
      - run: esphome compile ${{ matrix.file }}
 | 
			
		||||
        if: matrix.id == 'test'
 | 
			
		||||
        env:
 | 
			
		||||
          # Also cache libdeps, store them in a ~/.platformio subfolder
 | 
			
		||||
          PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
 | 
			
		||||
 | 
			
		||||
      - name: Run pytest
 | 
			
		||||
        run: |
 | 
			
		||||
          pytest -vv --tb=native tests
 | 
			
		||||
        if: matrix.id == 'pytest'
 | 
			
		||||
 | 
			
		||||
      # Also run git-diff-index so that the step is marked as failed on
 | 
			
		||||
      # formatting errors, since clang-format doesn't do anything but
 | 
			
		||||
      # change files if -i is passed.
 | 
			
		||||
      - name: Run clang-format
 | 
			
		||||
        run: |
 | 
			
		||||
          script/clang-format -i
 | 
			
		||||
          git diff-index --quiet HEAD --
 | 
			
		||||
        if: matrix.id == 'clang-format'
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-tidy
 | 
			
		||||
        run: |
 | 
			
		||||
          script/clang-tidy --all-headers --fix ${{ matrix.options }}
 | 
			
		||||
        if: matrix.id == 'clang-tidy'
 | 
			
		||||
        env:
 | 
			
		||||
          # Also cache libdeps, store them in a ~/.platformio subfolder
 | 
			
		||||
          PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
 | 
			
		||||
 | 
			
		||||
      - name: Run yamllint
 | 
			
		||||
        if: matrix.id == 'yamllint'
 | 
			
		||||
        uses: frenck/action-yamllint@v1.4.0
 | 
			
		||||
 | 
			
		||||
      - name: Suggested changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
        # yamllint disable-line rule:line-length
 | 
			
		||||
        if: always()
 | 
			
		||||
        if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python')
 | 
			
		||||
 | 
			
		||||
  ci-status:
 | 
			
		||||
    name: CI Status
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
      - black
 | 
			
		||||
      - ci-custom
 | 
			
		||||
      - clang-format
 | 
			
		||||
      - flake8
 | 
			
		||||
      - pylint
 | 
			
		||||
      - pytest
 | 
			
		||||
      - pyupgrade
 | 
			
		||||
      - yamllint
 | 
			
		||||
      - compile-tests
 | 
			
		||||
      - clang-tidy
 | 
			
		||||
    needs: [ci]
 | 
			
		||||
    if: always()
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Success
 | 
			
		||||
      - name: Successful deploy
 | 
			
		||||
        if: ${{ !(contains(needs.*.result, 'failure')) }}
 | 
			
		||||
        run: exit 0
 | 
			
		||||
      - name: Failure
 | 
			
		||||
      - name: Failing deploy
 | 
			
		||||
        if: ${{ contains(needs.*.result, 'failure') }}
 | 
			
		||||
        run: exit 1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -49,11 +49,9 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: "3.x"
 | 
			
		||||
      - name: Set up python environment
 | 
			
		||||
        env:
 | 
			
		||||
          ESPHOME_NO_VENV: 1
 | 
			
		||||
        run: |
 | 
			
		||||
          script/setup
 | 
			
		||||
          pip install twine
 | 
			
		||||
          pip install setuptools wheel twine
 | 
			
		||||
      - name: Build
 | 
			
		||||
        run: python setup.py sdist bdist_wheel
 | 
			
		||||
      - name: Upload
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							@@ -26,7 +26,7 @@ jobs:
 | 
			
		||||
          days-before-issue-close: -1
 | 
			
		||||
          remove-stale-when-updated: true
 | 
			
		||||
          stale-pr-label: "stale"
 | 
			
		||||
          exempt-pr-labels: "not-stale"
 | 
			
		||||
          exempt-pr-labels: "no-stale"
 | 
			
		||||
          stale-pr-message: >
 | 
			
		||||
            There hasn't been any activity on this pull request recently. This
 | 
			
		||||
            pull request has been automatically marked as stale because of that
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							@@ -6,12 +6,14 @@ on:
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: '45 6 * * *'
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: write
 | 
			
		||||
  pull-requests: write
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  sync:
 | 
			
		||||
    name: Sync Device Classes
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
@@ -36,14 +38,23 @@ jobs:
 | 
			
		||||
        run: |
 | 
			
		||||
          python ./script/sync-device_class.py
 | 
			
		||||
 | 
			
		||||
      - name: Get PR template
 | 
			
		||||
        id: pr-template-body
 | 
			
		||||
        run: |
 | 
			
		||||
          body=$(cat .github/PULL_REQUEST_TEMPLATE.md)
 | 
			
		||||
          delimiter="$(openssl rand -hex 8)"
 | 
			
		||||
          echo "body<<$delimiter" >> $GITHUB_OUTPUT
 | 
			
		||||
          echo "$body" >> $GITHUB_OUTPUT
 | 
			
		||||
          echo "$delimiter" >> $GITHUB_OUTPUT
 | 
			
		||||
 | 
			
		||||
      - name: Commit changes
 | 
			
		||||
        uses: peter-evans/create-pull-request@v5
 | 
			
		||||
        with:
 | 
			
		||||
          commit-message: "Synchronise Device Classes from Home Assistant"
 | 
			
		||||
          committer: esphomebot <esphome@nabucasa.com>
 | 
			
		||||
          author: esphomebot <esphome@nabucasa.com>
 | 
			
		||||
          branch: sync/device-classes
 | 
			
		||||
          branch: sync/device-classes/
 | 
			
		||||
          branch-suffix: timestamp
 | 
			
		||||
          delete-branch: true
 | 
			
		||||
          title: "Synchronise Device Classes from Home Assistant"
 | 
			
		||||
          body-path: .github/PULL_REQUEST_TEMPLATE.md
 | 
			
		||||
          token: ${{ secrets.DEVICE_CLASS_SYNC_TOKEN }}
 | 
			
		||||
          body: ${{ steps.pr-template-body.outputs.body }}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ repos:
 | 
			
		||||
          - --branch=release
 | 
			
		||||
          - --branch=beta
 | 
			
		||||
  - repo: https://github.com/asottile/pyupgrade
 | 
			
		||||
    rev: v3.7.0
 | 
			
		||||
    rev: v3.3.2
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: pyupgrade
 | 
			
		||||
        args: [--py39-plus]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							@@ -36,24 +36,6 @@
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "label": "Generate proto files",
 | 
			
		||||
      "type": "shell",
 | 
			
		||||
      "command": "${command:python.interpreterPath}",
 | 
			
		||||
      "args": [
 | 
			
		||||
        "./script/api_protobuf/api_protobuf.py"
 | 
			
		||||
      ],
 | 
			
		||||
      "group": {
 | 
			
		||||
        "kind": "build",
 | 
			
		||||
        "isDefault": true
 | 
			
		||||
      },
 | 
			
		||||
      "presentation": {
 | 
			
		||||
        "reveal": "never",
 | 
			
		||||
        "close": true,
 | 
			
		||||
        "panel": "new"
 | 
			
		||||
      },
 | 
			
		||||
      "problemMatcher": []
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								CODEOWNERS
									
									
									
									
									
								
							@@ -17,11 +17,8 @@ esphome/components/adc/* @esphome/core
 | 
			
		||||
esphome/components/adc128s102/* @DeerMaximum
 | 
			
		||||
esphome/components/addressable_light/* @justfalter
 | 
			
		||||
esphome/components/airthings_ble/* @jeromelaban
 | 
			
		||||
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
 | 
			
		||||
esphome/components/airthings_wave_mini/* @ncareau
 | 
			
		||||
esphome/components/airthings_wave_plus/* @jeromelaban
 | 
			
		||||
esphome/components/alarm_control_panel/* @grahambrown11
 | 
			
		||||
esphome/components/alpha3/* @jan-hofmeier
 | 
			
		||||
esphome/components/am43/* @buxtronix
 | 
			
		||||
esphome/components/am43/cover/* @buxtronix
 | 
			
		||||
esphome/components/am43/sensor/* @buxtronix
 | 
			
		||||
@@ -32,7 +29,6 @@ esphome/components/api/* @OttoWinter
 | 
			
		||||
esphome/components/as7341/* @mrgnr
 | 
			
		||||
esphome/components/async_tcp/* @OttoWinter
 | 
			
		||||
esphome/components/atc_mithermometer/* @ahpohl
 | 
			
		||||
esphome/components/atm90e26/* @danieltwagner
 | 
			
		||||
esphome/components/b_parasite/* @rbaron
 | 
			
		||||
esphome/components/ballu/* @bazuchan
 | 
			
		||||
esphome/components/bang_bang/* @OttoWinter
 | 
			
		||||
@@ -78,7 +74,6 @@ esphome/components/display_menu_base/* @numo68
 | 
			
		||||
esphome/components/dps310/* @kbx81
 | 
			
		||||
esphome/components/ds1307/* @badbadc0ffee
 | 
			
		||||
esphome/components/dsmr/* @glmnet @zuidwijk
 | 
			
		||||
esphome/components/duty_time/* @dudanov
 | 
			
		||||
esphome/components/ee895/* @Stock-M
 | 
			
		||||
esphome/components/ektf2232/* @jesserockz
 | 
			
		||||
esphome/components/ens210/* @itn3rd77
 | 
			
		||||
@@ -89,7 +84,6 @@ esphome/components/esp32_ble_server/* @jesserockz
 | 
			
		||||
esphome/components/esp32_camera_web_server/* @ayufan
 | 
			
		||||
esphome/components/esp32_can/* @Sympatron
 | 
			
		||||
esphome/components/esp32_improv/* @jesserockz
 | 
			
		||||
esphome/components/esp32_rmt_led_strip/* @jesserockz
 | 
			
		||||
esphome/components/esp8266/* @esphome/core
 | 
			
		||||
esphome/components/ethernet_info/* @gtjadsonsantos
 | 
			
		||||
esphome/components/exposure_notifications/* @OttoWinter
 | 
			
		||||
@@ -101,22 +95,18 @@ esphome/components/feedback/* @ianchi
 | 
			
		||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
 | 
			
		||||
esphome/components/fs3000/* @kahrendt
 | 
			
		||||
esphome/components/globals/* @esphome/core
 | 
			
		||||
esphome/components/gp8403/* @jesserockz
 | 
			
		||||
esphome/components/gpio/* @esphome/core
 | 
			
		||||
esphome/components/gps/* @coogle
 | 
			
		||||
esphome/components/graph/* @synco
 | 
			
		||||
esphome/components/grove_tb6612fng/* @max246
 | 
			
		||||
esphome/components/growatt_solar/* @leeuwte
 | 
			
		||||
esphome/components/haier/* @paveldn
 | 
			
		||||
esphome/components/haier/* @Yarikx
 | 
			
		||||
esphome/components/havells_solar/* @sourabhjaiswal
 | 
			
		||||
esphome/components/hbridge/fan/* @WeekendWarrior
 | 
			
		||||
esphome/components/hbridge/light/* @DotNetDann
 | 
			
		||||
esphome/components/heatpumpir/* @rob-deutsch
 | 
			
		||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
 | 
			
		||||
esphome/components/hm3301/* @freekode
 | 
			
		||||
esphome/components/homeassistant/* @OttoWinter
 | 
			
		||||
esphome/components/honeywellabp/* @RubyBailey
 | 
			
		||||
esphome/components/host/* @esphome/core
 | 
			
		||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
 | 
			
		||||
esphome/components/hte501/* @Stock-M
 | 
			
		||||
esphome/components/hydreon_rgxx/* @functionpointer
 | 
			
		||||
@@ -125,7 +115,6 @@ esphome/components/i2c/* @esphome/core
 | 
			
		||||
esphome/components/i2s_audio/* @jesserockz
 | 
			
		||||
esphome/components/i2s_audio/media_player/* @jesserockz
 | 
			
		||||
esphome/components/i2s_audio/microphone/* @jesserockz
 | 
			
		||||
esphome/components/i2s_audio/speaker/* @jesserockz
 | 
			
		||||
esphome/components/ili9xxx/* @nielsnl68
 | 
			
		||||
esphome/components/improv_base/* @esphome/core
 | 
			
		||||
esphome/components/improv_serial/* @esphome/core
 | 
			
		||||
@@ -204,7 +193,6 @@ esphome/components/output/* @esphome/core
 | 
			
		||||
esphome/components/pca6416a/* @Mat931
 | 
			
		||||
esphome/components/pca9554/* @hwstar
 | 
			
		||||
esphome/components/pcf85063/* @brogon
 | 
			
		||||
esphome/components/pcf8563/* @KoenBreeman
 | 
			
		||||
esphome/components/pid/* @OttoWinter
 | 
			
		||||
esphome/components/pipsolar/* @andreashergert1984
 | 
			
		||||
esphome/components/pm1006/* @habbie
 | 
			
		||||
@@ -228,7 +216,6 @@ esphome/components/restart/* @esphome/core
 | 
			
		||||
esphome/components/rf_bridge/* @jesserockz
 | 
			
		||||
esphome/components/rgbct/* @jesserockz
 | 
			
		||||
esphome/components/rp2040/* @jesserockz
 | 
			
		||||
esphome/components/rp2040_pio_led_strip/* @Papa-DMan
 | 
			
		||||
esphome/components/rp2040_pwm/* @jesserockz
 | 
			
		||||
esphome/components/rtttl/* @glmnet
 | 
			
		||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
 | 
			
		||||
@@ -250,7 +237,7 @@ esphome/components/shutdown/* @esphome/core @jsuanet
 | 
			
		||||
esphome/components/sigma_delta_output/* @Cat-Ion
 | 
			
		||||
esphome/components/sim800l/* @glmnet
 | 
			
		||||
esphome/components/sm10bit_base/* @Cossid
 | 
			
		||||
esphome/components/sm2135/* @BoukeHaarsma23 @dd32 @matika77
 | 
			
		||||
esphome/components/sm2135/* @BoukeHaarsma23
 | 
			
		||||
esphome/components/sm2235/* @Cossid
 | 
			
		||||
esphome/components/sm2335/* @Cossid
 | 
			
		||||
esphome/components/sml/* @alengwenus
 | 
			
		||||
@@ -258,7 +245,6 @@ esphome/components/smt100/* @piechade
 | 
			
		||||
esphome/components/sn74hc165/* @jesserockz
 | 
			
		||||
esphome/components/socket/* @esphome/core
 | 
			
		||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
 | 
			
		||||
esphome/components/speaker/* @jesserockz
 | 
			
		||||
esphome/components/spi/* @esphome/core
 | 
			
		||||
esphome/components/sprinkler/* @kbx81
 | 
			
		||||
esphome/components/sps30/* @martgras
 | 
			
		||||
@@ -284,22 +270,18 @@ esphome/components/tca9548a/* @andreashergert1984
 | 
			
		||||
esphome/components/tcl112/* @glmnet
 | 
			
		||||
esphome/components/tee501/* @Stock-M
 | 
			
		||||
esphome/components/teleinfo/* @0hax
 | 
			
		||||
esphome/components/template/alarm_control_panel/* @grahambrown11
 | 
			
		||||
esphome/components/thermostat/* @kbx81
 | 
			
		||||
esphome/components/time/* @OttoWinter
 | 
			
		||||
esphome/components/tlc5947/* @rnauber
 | 
			
		||||
esphome/components/tm1621/* @Philippe12
 | 
			
		||||
esphome/components/tm1637/* @glmnet
 | 
			
		||||
esphome/components/tm1638/* @skykingjwc
 | 
			
		||||
esphome/components/tm1651/* @freekode
 | 
			
		||||
esphome/components/tmp102/* @timsavage
 | 
			
		||||
esphome/components/tmp1075/* @sybrenstuvel
 | 
			
		||||
esphome/components/tmp117/* @Azimath
 | 
			
		||||
esphome/components/tof10120/* @wstrzalka
 | 
			
		||||
esphome/components/toshiba/* @kbx81
 | 
			
		||||
esphome/components/touchscreen/* @jesserockz
 | 
			
		||||
esphome/components/tsl2591/* @wjcarpenter
 | 
			
		||||
esphome/components/tt21100/* @kroimon
 | 
			
		||||
esphome/components/tuya/binary_sensor/* @jesserockz
 | 
			
		||||
esphome/components/tuya/climate/* @jesserockz
 | 
			
		||||
esphome/components/tuya/number/* @frankiboy1
 | 
			
		||||
@@ -316,7 +298,6 @@ esphome/components/version/* @esphome/core
 | 
			
		||||
esphome/components/voice_assistant/* @jesserockz
 | 
			
		||||
esphome/components/wake_on_lan/* @willwill2will54
 | 
			
		||||
esphome/components/web_server_base/* @OttoWinter
 | 
			
		||||
esphome/components/web_server_idf/* @dentra
 | 
			
		||||
esphome/components/whirlpool/* @glmnet
 | 
			
		||||
esphome/components/whynter/* @aeonsablaze
 | 
			
		||||
esphome/components/wiegand/* @ssieb
 | 
			
		||||
@@ -326,6 +307,4 @@ esphome/components/xiaomi_lywsd03mmc/* @ahpohl
 | 
			
		||||
esphome/components/xiaomi_mhoc303/* @drug123
 | 
			
		||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
 | 
			
		||||
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
 | 
			
		||||
esphome/components/xl9535/* @mreditor97
 | 
			
		||||
esphome/components/xpt2046/* @nielsnl68 @numo68
 | 
			
		||||
esphome/components/zio_ultrasonic/* @kahrendt
 | 
			
		||||
 
 | 
			
		||||
@@ -29,8 +29,6 @@ RUN \
 | 
			
		||||
        git=1:2.30.2-1+deb11u2 \
 | 
			
		||||
        curl=7.74.0-1.3+deb11u7 \
 | 
			
		||||
        openssh-client=1:8.4p1-5+deb11u1 \
 | 
			
		||||
        libcairo2=1.16.0-5 \
 | 
			
		||||
        python3-cffi=1.14.5-1 \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
        /tmp/* \
 | 
			
		||||
        /var/{cache,log}/* \
 | 
			
		||||
@@ -54,7 +52,7 @@ RUN \
 | 
			
		||||
    # Ubuntu python3-pip is missing wheel
 | 
			
		||||
    pip3 install --no-cache-dir \
 | 
			
		||||
        wheel==0.37.1 \
 | 
			
		||||
        platformio==6.1.7 \
 | 
			
		||||
        platformio==6.1.6 \
 | 
			
		||||
    # Change some platformio settings
 | 
			
		||||
    && platformio settings set enable_telemetry No \
 | 
			
		||||
    && platformio settings set check_platformio_interval 1000000 \
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_LOGGER,
 | 
			
		||||
    CONF_NAME,
 | 
			
		||||
    CONF_OTA,
 | 
			
		||||
    CONF_MQTT,
 | 
			
		||||
    CONF_MDNS,
 | 
			
		||||
    CONF_DISABLED,
 | 
			
		||||
    CONF_PASSWORD,
 | 
			
		||||
    CONF_PORT,
 | 
			
		||||
    CONF_ESPHOME,
 | 
			
		||||
@@ -32,7 +29,7 @@ from esphome.const import (
 | 
			
		||||
    SECRETS_FILES,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, EsphomeError, coroutine
 | 
			
		||||
from esphome.helpers import indent, is_ip_address
 | 
			
		||||
from esphome.helpers import indent
 | 
			
		||||
from esphome.util import (
 | 
			
		||||
    run_external_command,
 | 
			
		||||
    run_external_process,
 | 
			
		||||
@@ -45,7 +42,7 @@ from esphome.log import color, setup_log, Fore
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def choose_prompt(options, purpose: str = None):
 | 
			
		||||
def choose_prompt(options):
 | 
			
		||||
    if not options:
 | 
			
		||||
        raise EsphomeError(
 | 
			
		||||
            "Found no valid options for upload/logging, please make sure relevant "
 | 
			
		||||
@@ -56,9 +53,7 @@ def choose_prompt(options, purpose: str = None):
 | 
			
		||||
    if len(options) == 1:
 | 
			
		||||
        return options[0][1]
 | 
			
		||||
 | 
			
		||||
    safe_print(
 | 
			
		||||
        f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:'
 | 
			
		||||
    )
 | 
			
		||||
    safe_print("Found multiple options, please choose one:")
 | 
			
		||||
    for i, (desc, _) in enumerate(options):
 | 
			
		||||
        safe_print(f"  [{i+1}] {desc}")
 | 
			
		||||
 | 
			
		||||
@@ -77,9 +72,7 @@ def choose_prompt(options, purpose: str = None):
 | 
			
		||||
    return options[opt - 1][1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def choose_upload_log_host(
 | 
			
		||||
    default, check_default, show_ota, show_mqtt, show_api, purpose: str = None
 | 
			
		||||
):
 | 
			
		||||
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
 | 
			
		||||
    options = []
 | 
			
		||||
    for port in get_serial_ports():
 | 
			
		||||
        options.append((f"{port.path} ({port.description})", port.path))
 | 
			
		||||
@@ -87,7 +80,7 @@ def choose_upload_log_host(
 | 
			
		||||
        options.append((f"Over The Air ({CORE.address})", CORE.address))
 | 
			
		||||
        if default == "OTA":
 | 
			
		||||
            return CORE.address
 | 
			
		||||
    if show_mqtt and CONF_MQTT in CORE.config:
 | 
			
		||||
    if show_mqtt and "mqtt" in CORE.config:
 | 
			
		||||
        options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT"))
 | 
			
		||||
        if default == "OTA":
 | 
			
		||||
            return "MQTT"
 | 
			
		||||
@@ -95,7 +88,7 @@ def choose_upload_log_host(
 | 
			
		||||
        return default
 | 
			
		||||
    if check_default is not None and check_default in [opt[1] for opt in options]:
 | 
			
		||||
        return check_default
 | 
			
		||||
    return choose_prompt(options, purpose=purpose)
 | 
			
		||||
    return choose_prompt(options)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_port_type(port):
 | 
			
		||||
@@ -295,32 +288,19 @@ def upload_program(config, args, host):
 | 
			
		||||
 | 
			
		||||
        return 1  # Unknown target platform
 | 
			
		||||
 | 
			
		||||
    from esphome import espota2
 | 
			
		||||
 | 
			
		||||
    if CONF_OTA not in config:
 | 
			
		||||
        raise EsphomeError(
 | 
			
		||||
            "Cannot upload Over the Air as the config does not include the ota: "
 | 
			
		||||
            "component"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    from esphome import espota2
 | 
			
		||||
 | 
			
		||||
    ota_conf = config[CONF_OTA]
 | 
			
		||||
    remote_port = ota_conf[CONF_PORT]
 | 
			
		||||
    password = ota_conf.get(CONF_PASSWORD, "")
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        not is_ip_address(CORE.address)
 | 
			
		||||
        and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
 | 
			
		||||
        and CONF_MQTT in config
 | 
			
		||||
    ):
 | 
			
		||||
        from esphome import mqtt
 | 
			
		||||
 | 
			
		||||
        host = mqtt.get_esphome_device_ip(
 | 
			
		||||
            config, args.username, args.password, args.client_id
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if getattr(args, "file", None) is not None:
 | 
			
		||||
        return espota2.run_ota(host, remote_port, password, args.file)
 | 
			
		||||
 | 
			
		||||
    return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -330,13 +310,6 @@ def show_logs(config, args, port):
 | 
			
		||||
    if get_port_type(port) == "SERIAL":
 | 
			
		||||
        return run_miniterm(config, port)
 | 
			
		||||
    if get_port_type(port) == "NETWORK" and "api" in config:
 | 
			
		||||
        if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
 | 
			
		||||
            from esphome import mqtt
 | 
			
		||||
 | 
			
		||||
            port = mqtt.get_esphome_device_ip(
 | 
			
		||||
                config, args.username, args.password, args.client_id
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        from esphome.components.api.client import run_logs
 | 
			
		||||
 | 
			
		||||
        return run_logs(config, port)
 | 
			
		||||
@@ -401,7 +374,6 @@ def command_upload(args, config):
 | 
			
		||||
        show_ota=True,
 | 
			
		||||
        show_mqtt=False,
 | 
			
		||||
        show_api=False,
 | 
			
		||||
        purpose="uploading",
 | 
			
		||||
    )
 | 
			
		||||
    exit_code = upload_program(config, args, port)
 | 
			
		||||
    if exit_code != 0:
 | 
			
		||||
@@ -410,15 +382,6 @@ def command_upload(args, config):
 | 
			
		||||
    return 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def command_discover(args, config):
 | 
			
		||||
    if "mqtt" in config:
 | 
			
		||||
        from esphome import mqtt
 | 
			
		||||
 | 
			
		||||
        return mqtt.show_discover(config, args.username, args.password, args.client_id)
 | 
			
		||||
 | 
			
		||||
    raise EsphomeError("No discover method configured (mqtt)")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def command_logs(args, config):
 | 
			
		||||
    port = choose_upload_log_host(
 | 
			
		||||
        default=args.device,
 | 
			
		||||
@@ -426,7 +389,6 @@ def command_logs(args, config):
 | 
			
		||||
        show_ota=False,
 | 
			
		||||
        show_mqtt=True,
 | 
			
		||||
        show_api=True,
 | 
			
		||||
        purpose="logging",
 | 
			
		||||
    )
 | 
			
		||||
    return show_logs(config, args, port)
 | 
			
		||||
 | 
			
		||||
@@ -445,7 +407,6 @@ def command_run(args, config):
 | 
			
		||||
        show_ota=True,
 | 
			
		||||
        show_mqtt=False,
 | 
			
		||||
        show_api=True,
 | 
			
		||||
        purpose="uploading",
 | 
			
		||||
    )
 | 
			
		||||
    exit_code = upload_program(config, args, port)
 | 
			
		||||
    if exit_code != 0:
 | 
			
		||||
@@ -459,7 +420,6 @@ def command_run(args, config):
 | 
			
		||||
        show_ota=False,
 | 
			
		||||
        show_mqtt=True,
 | 
			
		||||
        show_api=True,
 | 
			
		||||
        purpose="logging",
 | 
			
		||||
    )
 | 
			
		||||
    return show_logs(config, args, port)
 | 
			
		||||
 | 
			
		||||
@@ -663,7 +623,6 @@ POST_CONFIG_ACTIONS = {
 | 
			
		||||
    "clean": command_clean,
 | 
			
		||||
    "idedata": command_idedata,
 | 
			
		||||
    "rename": command_rename,
 | 
			
		||||
    "discover": command_discover,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -752,15 +711,6 @@ def parse_args(argv):
 | 
			
		||||
        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser_discover = subparsers.add_parser(
 | 
			
		||||
        "discover",
 | 
			
		||||
        help="Validate the configuration and show all discovered devices.",
 | 
			
		||||
        parents=[mqtt_options],
 | 
			
		||||
    )
 | 
			
		||||
    parser_discover.add_argument(
 | 
			
		||||
        "configuration", help="Your YAML configuration file.", nargs=1
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser_run = subparsers.add_parser(
 | 
			
		||||
        "run",
 | 
			
		||||
        help="Validate the configuration, create a binary, upload it, and start logs.",
 | 
			
		||||
@@ -982,8 +932,6 @@ def run_esphome(argv):
 | 
			
		||||
            _LOGGER.error(e, exc_info=args.verbose)
 | 
			
		||||
            return 1
 | 
			
		||||
 | 
			
		||||
    _LOGGER.info("ESPHome %s", const.__version__)
 | 
			
		||||
 | 
			
		||||
    for conf_path in args.configuration:
 | 
			
		||||
        if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
 | 
			
		||||
            _LOGGER.warning("Skipping secrets file %s", conf_path)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,169 +1 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import CONF_INPUT
 | 
			
		||||
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome.components.esp32 import get_esp32_variant
 | 
			
		||||
from esphome.components.esp32.const import (
 | 
			
		||||
    VARIANT_ESP32,
 | 
			
		||||
    VARIANT_ESP32C3,
 | 
			
		||||
    VARIANT_ESP32H2,
 | 
			
		||||
    VARIANT_ESP32S2,
 | 
			
		||||
    VARIANT_ESP32S3,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
 | 
			
		||||
ATTENUATION_MODES = {
 | 
			
		||||
    "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,
 | 
			
		||||
    "auto": "auto",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
 | 
			
		||||
adc2_channel_t = cg.global_ns.enum("adc2_channel_t")
 | 
			
		||||
 | 
			
		||||
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
 | 
			
		||||
# pin to adc1 channel mapping
 | 
			
		||||
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
			
		||||
    VARIANT_ESP32: {
 | 
			
		||||
        36: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        37: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        38: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        39: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        32: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        33: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        34: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        35: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32S2: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32S3: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32H2: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
			
		||||
    # TODO: add other variants
 | 
			
		||||
    VARIANT_ESP32: {
 | 
			
		||||
        4: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
        0: adc2_channel_t.ADC2_CHANNEL_1,
 | 
			
		||||
        2: adc2_channel_t.ADC2_CHANNEL_2,
 | 
			
		||||
        15: adc2_channel_t.ADC2_CHANNEL_3,
 | 
			
		||||
        13: adc2_channel_t.ADC2_CHANNEL_4,
 | 
			
		||||
        12: adc2_channel_t.ADC2_CHANNEL_5,
 | 
			
		||||
        14: adc2_channel_t.ADC2_CHANNEL_6,
 | 
			
		||||
        27: adc2_channel_t.ADC2_CHANNEL_7,
 | 
			
		||||
        25: adc2_channel_t.ADC2_CHANNEL_8,
 | 
			
		||||
        26: adc2_channel_t.ADC2_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32S2: {
 | 
			
		||||
        11: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
        12: adc2_channel_t.ADC2_CHANNEL_1,
 | 
			
		||||
        13: adc2_channel_t.ADC2_CHANNEL_2,
 | 
			
		||||
        14: adc2_channel_t.ADC2_CHANNEL_3,
 | 
			
		||||
        15: adc2_channel_t.ADC2_CHANNEL_4,
 | 
			
		||||
        16: adc2_channel_t.ADC2_CHANNEL_5,
 | 
			
		||||
        17: adc2_channel_t.ADC2_CHANNEL_6,
 | 
			
		||||
        18: adc2_channel_t.ADC2_CHANNEL_7,
 | 
			
		||||
        19: adc2_channel_t.ADC2_CHANNEL_8,
 | 
			
		||||
        20: adc2_channel_t.ADC2_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32S3: {
 | 
			
		||||
        11: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
        12: adc2_channel_t.ADC2_CHANNEL_1,
 | 
			
		||||
        13: adc2_channel_t.ADC2_CHANNEL_2,
 | 
			
		||||
        14: adc2_channel_t.ADC2_CHANNEL_3,
 | 
			
		||||
        15: adc2_channel_t.ADC2_CHANNEL_4,
 | 
			
		||||
        16: adc2_channel_t.ADC2_CHANNEL_5,
 | 
			
		||||
        17: adc2_channel_t.ADC2_CHANNEL_6,
 | 
			
		||||
        18: adc2_channel_t.ADC2_CHANNEL_7,
 | 
			
		||||
        19: adc2_channel_t.ADC2_CHANNEL_8,
 | 
			
		||||
        20: adc2_channel_t.ADC2_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        5: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_adc_pin(value):
 | 
			
		||||
    if str(value).upper() == "VCC":
 | 
			
		||||
        return cv.only_on_esp8266("VCC")
 | 
			
		||||
 | 
			
		||||
    if str(value).upper() == "TEMPERATURE":
 | 
			
		||||
        return cv.only_on_rp2040("TEMPERATURE")
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        value = pins.internal_gpio_input_pin_number(value)
 | 
			
		||||
        variant = get_esp32_variant()
 | 
			
		||||
        if (
 | 
			
		||||
            variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL
 | 
			
		||||
            and variant not in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL
 | 
			
		||||
        ):
 | 
			
		||||
            raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
            value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]
 | 
			
		||||
            and value not in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant]
 | 
			
		||||
        ):
 | 
			
		||||
            raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
 | 
			
		||||
 | 
			
		||||
        return pins.internal_gpio_input_pin_schema(value)
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        from esphome.components.esp8266.gpio import CONF_ANALOG
 | 
			
		||||
 | 
			
		||||
        value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
 | 
			
		||||
            value
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if value != 17:  # A0
 | 
			
		||||
            raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC")
 | 
			
		||||
        return pins.gpio_pin_schema(
 | 
			
		||||
            {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
 | 
			
		||||
        )(value)
 | 
			
		||||
 | 
			
		||||
    if CORE.is_rp2040:
 | 
			
		||||
        value = pins.internal_gpio_input_pin_number(value)
 | 
			
		||||
        if value not in (26, 27, 28, 29):
 | 
			
		||||
            raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC")
 | 
			
		||||
        return pins.internal_gpio_input_pin_schema(value)
 | 
			
		||||
 | 
			
		||||
    raise NotImplementedError
 | 
			
		||||
 
 | 
			
		||||
@@ -20,15 +20,15 @@ namespace adc {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "adc";
 | 
			
		||||
 | 
			
		||||
// 13-bit for S2, 12-bit for all other ESP32 variants
 | 
			
		||||
// 13bit for S2, and 12bit for all other esp32 variants
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
 | 
			
		||||
 | 
			
		||||
#ifndef SOC_ADC_RTC_MAX_BITWIDTH
 | 
			
		||||
#if USE_ESP32_VARIANT_ESP32S2
 | 
			
		||||
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13;
 | 
			
		||||
static const int SOC_ADC_RTC_MAX_BITWIDTH = 13;
 | 
			
		||||
#else
 | 
			
		||||
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12;
 | 
			
		||||
static const int SOC_ADC_RTC_MAX_BITWIDTH = 12;
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -47,21 +47,14 @@ extern "C"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  if (channel1_ != ADC1_CHANNEL_MAX) {
 | 
			
		||||
    adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
 | 
			
		||||
    if (!autorange_) {
 | 
			
		||||
      adc1_config_channel_atten(channel1_, attenuation_);
 | 
			
		||||
    }
 | 
			
		||||
  } else if (channel2_ != ADC2_CHANNEL_MAX) {
 | 
			
		||||
    if (!autorange_) {
 | 
			
		||||
      adc2_config_channel_atten(channel2_, attenuation_);
 | 
			
		||||
    }
 | 
			
		||||
  adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
 | 
			
		||||
  if (!autorange_) {
 | 
			
		||||
    adc1_config_channel_atten(channel_, attenuation_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // load characteristics for each attenuation
 | 
			
		||||
  for (int32_t i = 0; i <= ADC_ATTEN_DB_11; i++) {
 | 
			
		||||
    auto adc_unit = channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2;
 | 
			
		||||
    auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
 | 
			
		||||
  for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) {
 | 
			
		||||
    auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
 | 
			
		||||
                                              1100,  // default vref
 | 
			
		||||
                                              &cal_characteristics_[i]);
 | 
			
		||||
    switch (cal_value) {
 | 
			
		||||
@@ -143,9 +136,9 @@ void ADCSensor::update() {
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
float ADCSensor::sample() {
 | 
			
		||||
#ifdef USE_ADC_SENSOR_VCC
 | 
			
		||||
  int32_t raw = ESP.getVcc();  // NOLINT(readability-static-accessed-through-instance)
 | 
			
		||||
  int raw = ESP.getVcc();  // NOLINT(readability-static-accessed-through-instance)
 | 
			
		||||
#else
 | 
			
		||||
  int32_t raw = analogRead(this->pin_->get_pin());  // NOLINT
 | 
			
		||||
  int raw = analogRead(this->pin_->get_pin());  // NOLINT
 | 
			
		||||
#endif
 | 
			
		||||
  if (output_raw_) {
 | 
			
		||||
    return raw;
 | 
			
		||||
@@ -157,53 +150,29 @@ float ADCSensor::sample() {
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
float ADCSensor::sample() {
 | 
			
		||||
  if (!autorange_) {
 | 
			
		||||
    int raw = -1;
 | 
			
		||||
    if (channel1_ != ADC1_CHANNEL_MAX) {
 | 
			
		||||
      raw = adc1_get_raw(channel1_);
 | 
			
		||||
    } else if (channel2_ != ADC2_CHANNEL_MAX) {
 | 
			
		||||
      adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int raw = adc1_get_raw(channel_);
 | 
			
		||||
    if (raw == -1) {
 | 
			
		||||
      return NAN;
 | 
			
		||||
    }
 | 
			
		||||
    if (output_raw_) {
 | 
			
		||||
      return raw;
 | 
			
		||||
    }
 | 
			
		||||
    uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int32_t) attenuation_]);
 | 
			
		||||
    uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]);
 | 
			
		||||
    return mv / 1000.0f;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int raw11 = ADC_MAX, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX;
 | 
			
		||||
 | 
			
		||||
  if (channel1_ != ADC1_CHANNEL_MAX) {
 | 
			
		||||
    adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_11);
 | 
			
		||||
    raw11 = adc1_get_raw(channel1_);
 | 
			
		||||
    if (raw11 < ADC_MAX) {
 | 
			
		||||
      adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_6);
 | 
			
		||||
      raw6 = adc1_get_raw(channel1_);
 | 
			
		||||
      if (raw6 < ADC_MAX) {
 | 
			
		||||
        adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_2_5);
 | 
			
		||||
        raw2 = adc1_get_raw(channel1_);
 | 
			
		||||
        if (raw2 < ADC_MAX) {
 | 
			
		||||
          adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_0);
 | 
			
		||||
          raw0 = adc1_get_raw(channel1_);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else if (channel2_ != ADC2_CHANNEL_MAX) {
 | 
			
		||||
    adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_11);
 | 
			
		||||
    adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw11);
 | 
			
		||||
    if (raw11 < ADC_MAX) {
 | 
			
		||||
      adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_6);
 | 
			
		||||
      adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw6);
 | 
			
		||||
      if (raw6 < ADC_MAX) {
 | 
			
		||||
        adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_2_5);
 | 
			
		||||
        adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw2);
 | 
			
		||||
        if (raw2 < ADC_MAX) {
 | 
			
		||||
          adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_0);
 | 
			
		||||
          adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw0);
 | 
			
		||||
        }
 | 
			
		||||
  int raw11, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX;
 | 
			
		||||
  adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
 | 
			
		||||
  raw11 = adc1_get_raw(channel_);
 | 
			
		||||
  if (raw11 < ADC_MAX) {
 | 
			
		||||
    adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6);
 | 
			
		||||
    raw6 = adc1_get_raw(channel_);
 | 
			
		||||
    if (raw6 < ADC_MAX) {
 | 
			
		||||
      adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5);
 | 
			
		||||
      raw2 = adc1_get_raw(channel_);
 | 
			
		||||
      if (raw2 < ADC_MAX) {
 | 
			
		||||
        adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0);
 | 
			
		||||
        raw0 = adc1_get_raw(channel_);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -212,10 +181,10 @@ float ADCSensor::sample() {
 | 
			
		||||
    return NAN;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_11]);
 | 
			
		||||
  uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_6]);
 | 
			
		||||
  uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]);
 | 
			
		||||
  uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]);
 | 
			
		||||
  uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]);
 | 
			
		||||
  uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]);
 | 
			
		||||
  uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]);
 | 
			
		||||
  uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]);
 | 
			
		||||
 | 
			
		||||
  // Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC)
 | 
			
		||||
  uint32_t c11 = std::min(raw11, ADC_HALF);
 | 
			
		||||
@@ -243,7 +212,7 @@ float ADCSensor::sample() {
 | 
			
		||||
    adc_select_input(pin - 26);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int32_t raw = adc_read();
 | 
			
		||||
  int raw = adc_read();
 | 
			
		||||
  if (this->is_temperature_) {
 | 
			
		||||
    adc_set_temp_sensor_enabled(false);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -19,23 +19,16 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  /// Set the attenuation for this pin. Only available on the ESP32.
 | 
			
		||||
  void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; }
 | 
			
		||||
  void set_channel1(adc1_channel_t channel) {
 | 
			
		||||
    channel1_ = channel;
 | 
			
		||||
    channel2_ = ADC2_CHANNEL_MAX;
 | 
			
		||||
  }
 | 
			
		||||
  void set_channel2(adc2_channel_t channel) {
 | 
			
		||||
    channel2_ = channel;
 | 
			
		||||
    channel1_ = ADC1_CHANNEL_MAX;
 | 
			
		||||
  }
 | 
			
		||||
  void set_channel(adc1_channel_t channel) { channel_ = channel; }
 | 
			
		||||
  void set_autorange(bool autorange) { autorange_ = autorange; }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  /// Update ADC values
 | 
			
		||||
  /// Update adc values.
 | 
			
		||||
  void update() override;
 | 
			
		||||
  /// Setup ADC
 | 
			
		||||
  /// Setup ADc
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  /// `HARDWARE_LATE` setup priority
 | 
			
		||||
  /// `HARDWARE_LATE` setup priority.
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
 | 
			
		||||
  void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
 | 
			
		||||
@@ -59,10 +52,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  adc_atten_t attenuation_{ADC_ATTEN_DB_0};
 | 
			
		||||
  adc1_channel_t channel1_{ADC1_CHANNEL_MAX};
 | 
			
		||||
  adc2_channel_t channel2_{ADC2_CHANNEL_MAX};
 | 
			
		||||
  adc1_channel_t channel_{};
 | 
			
		||||
  bool autorange_{false};
 | 
			
		||||
  esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {};
 | 
			
		||||
  esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {};
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,49 +1,137 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import sensor, voltage_sampler
 | 
			
		||||
from esphome.components.esp32 import get_esp32_variant
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ATTENUATION,
 | 
			
		||||
    CONF_RAW,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INPUT,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
    CONF_PIN,
 | 
			
		||||
    CONF_RAW,
 | 
			
		||||
    CONF_WIFI,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
)
 | 
			
		||||
from . import (
 | 
			
		||||
    ATTENUATION_MODES,
 | 
			
		||||
    ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
 | 
			
		||||
    ESP32_VARIANT_ADC2_PIN_TO_CHANNEL,
 | 
			
		||||
    validate_adc_pin,
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome.components.esp32 import get_esp32_variant
 | 
			
		||||
from esphome.components.esp32.const import (
 | 
			
		||||
    VARIANT_ESP32,
 | 
			
		||||
    VARIANT_ESP32C3,
 | 
			
		||||
    VARIANT_ESP32H2,
 | 
			
		||||
    VARIANT_ESP32S2,
 | 
			
		||||
    VARIANT_ESP32S3,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["voltage_sampler"]
 | 
			
		||||
 | 
			
		||||
ATTENUATION_MODES = {
 | 
			
		||||
    "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,
 | 
			
		||||
    "auto": "auto",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
 | 
			
		||||
 | 
			
		||||
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
 | 
			
		||||
# pin to adc1 channel mapping
 | 
			
		||||
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
			
		||||
    VARIANT_ESP32: {
 | 
			
		||||
        36: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        37: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        38: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        39: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        32: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        33: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        34: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        35: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32S2: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32S3: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32H2: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_adc_pin(value):
 | 
			
		||||
    if str(value).upper() == "VCC":
 | 
			
		||||
        return cv.only_on_esp8266("VCC")
 | 
			
		||||
 | 
			
		||||
    if str(value).upper() == "TEMPERATURE":
 | 
			
		||||
        return cv.only_on_rp2040("TEMPERATURE")
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        value = pins.internal_gpio_input_pin_number(value)
 | 
			
		||||
        variant = get_esp32_variant()
 | 
			
		||||
        if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
 | 
			
		||||
            raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
 | 
			
		||||
 | 
			
		||||
        if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
 | 
			
		||||
            raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
 | 
			
		||||
        return pins.internal_gpio_input_pin_schema(value)
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        from esphome.components.esp8266.gpio import CONF_ANALOG
 | 
			
		||||
 | 
			
		||||
        value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
 | 
			
		||||
            value
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if value != 17:  # A0
 | 
			
		||||
            raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
 | 
			
		||||
        return pins.gpio_pin_schema(
 | 
			
		||||
            {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
 | 
			
		||||
        )(value)
 | 
			
		||||
 | 
			
		||||
    if CORE.is_rp2040:
 | 
			
		||||
        value = pins.internal_gpio_input_pin_number(value)
 | 
			
		||||
        if value not in (26, 27, 28, 29):
 | 
			
		||||
            raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
 | 
			
		||||
        return pins.internal_gpio_input_pin_schema(value)
 | 
			
		||||
 | 
			
		||||
    raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_config(config):
 | 
			
		||||
    if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
 | 
			
		||||
        raise cv.Invalid("Automatic attenuation cannot be used when raw output is set")
 | 
			
		||||
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def final_validate_config(config):
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        variant = get_esp32_variant()
 | 
			
		||||
        if (
 | 
			
		||||
            CONF_WIFI in fv.full_config.get()
 | 
			
		||||
            and config[CONF_PIN][CONF_NUMBER]
 | 
			
		||||
            in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant]
 | 
			
		||||
        ):
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"{variant} doesn't support ADC on this pin when Wi-Fi is configured"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.")
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -73,8 +161,6 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    validate_config,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = final_validate_config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
@@ -101,15 +187,5 @@ async def to_code(config):
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        variant = get_esp32_variant()
 | 
			
		||||
        pin_num = config[CONF_PIN][CONF_NUMBER]
 | 
			
		||||
        if (
 | 
			
		||||
            variant in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL
 | 
			
		||||
            and pin_num in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]
 | 
			
		||||
        ):
 | 
			
		||||
            chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
 | 
			
		||||
            cg.add(var.set_channel1(chan))
 | 
			
		||||
        elif (
 | 
			
		||||
            variant in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL
 | 
			
		||||
            and pin_num in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant]
 | 
			
		||||
        ):
 | 
			
		||||
            chan = ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant][pin_num]
 | 
			
		||||
            cg.add(var.set_channel2(chan))
 | 
			
		||||
        chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
 | 
			
		||||
        cg.add(var.set_channel(chan))
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,6 @@ async def to_code(config):
 | 
			
		||||
 | 
			
		||||
    if CONF_LAMBDA in config:
 | 
			
		||||
        lambda_ = await cg.process_lambda(
 | 
			
		||||
            config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
 | 
			
		||||
            config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_writer(lambda_))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,103 +0,0 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, ble_client
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_BATTERY_VOLTAGE,
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_PRESSURE,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    CONF_TVOC,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_PRESSURE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_HECTOPASCAL,
 | 
			
		||||
    UNIT_PARTS_PER_BILLION,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@ncareau", "@jeromelaban", "@kpfleming"]
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["ble_client"]
 | 
			
		||||
 | 
			
		||||
CONF_BATTERY_UPDATE_INTERVAL = "battery_update_interval"
 | 
			
		||||
 | 
			
		||||
airthings_wave_base_ns = cg.esphome_ns.namespace("airthings_wave_base")
 | 
			
		||||
AirthingsWaveBase = airthings_wave_base_ns.class_(
 | 
			
		||||
    "AirthingsWaveBase", cg.PollingComponent, ble_client.BLEClientNode
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BASE_SCHEMA = (
 | 
			
		||||
    sensor.SENSOR_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_PERCENT,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                device_class=DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_CELSIUS,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_HECTOPASCAL,
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                device_class=DEVICE_CLASS_PRESSURE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TVOC): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_PARTS_PER_BILLION,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_VOLT,
 | 
			
		||||
                accuracy_decimals=3,
 | 
			
		||||
                device_class=DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_BATTERY_UPDATE_INTERVAL,
 | 
			
		||||
                default="24h",
 | 
			
		||||
            ): cv.update_interval,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("5min"))
 | 
			
		||||
    .extend(ble_client.BLE_CLIENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def wave_base_to_code(var, config):
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
 | 
			
		||||
    if config_humidity := config.get(CONF_HUMIDITY):
 | 
			
		||||
        sens = await sensor.new_sensor(config_humidity)
 | 
			
		||||
        cg.add(var.set_humidity(sens))
 | 
			
		||||
    if config_temperature := config.get(CONF_TEMPERATURE):
 | 
			
		||||
        sens = await sensor.new_sensor(config_temperature)
 | 
			
		||||
        cg.add(var.set_temperature(sens))
 | 
			
		||||
    if config_pressure := config.get(CONF_PRESSURE):
 | 
			
		||||
        sens = await sensor.new_sensor(config_pressure)
 | 
			
		||||
        cg.add(var.set_pressure(sens))
 | 
			
		||||
    if config_tvoc := config.get(CONF_TVOC):
 | 
			
		||||
        sens = await sensor.new_sensor(config_tvoc)
 | 
			
		||||
        cg.add(var.set_tvoc(sens))
 | 
			
		||||
    if config_battery_voltage := config.get(CONF_BATTERY_VOLTAGE):
 | 
			
		||||
        sens = await sensor.new_sensor(config_battery_voltage)
 | 
			
		||||
        cg.add(var.set_battery_voltage(sens))
 | 
			
		||||
    if config_battery_update_interval := config.get(CONF_BATTERY_UPDATE_INTERVAL):
 | 
			
		||||
        cg.add(var.set_battery_update_interval(config_battery_update_interval))
 | 
			
		||||
@@ -1,211 +0,0 @@
 | 
			
		||||
#include "airthings_wave_base.h"
 | 
			
		||||
 | 
			
		||||
// All information related to reading battery information came from the sensors.airthings_wave
 | 
			
		||||
// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace airthings_wave_base {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "airthings_wave_base";
 | 
			
		||||
 | 
			
		||||
void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                            esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      if (param->open.status == ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGI(TAG, "Connected successfully!");
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
      this->handle_ = 0;
 | 
			
		||||
      this->acp_handle_ = 0;
 | 
			
		||||
      this->cccd_handle_ = 0;
 | 
			
		||||
      ESP_LOGW(TAG, "Disconnected!");
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
      if (this->request_read_values_()) {
 | 
			
		||||
        if (!this->read_battery_next_update_) {
 | 
			
		||||
          this->node_state = espbt::ClientState::ESTABLISHED;
 | 
			
		||||
        } else {
 | 
			
		||||
          // delay setting node_state to ESTABLISHED until confirmation of the notify registration
 | 
			
		||||
          this->request_battery_();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // ensure that the client will be disconnected even if no responses arrive
 | 
			
		||||
      this->set_response_timeout_();
 | 
			
		||||
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_READ_CHAR_EVT: {
 | 
			
		||||
      if (param->read.conn_id != this->parent()->get_conn_id())
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->read.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      if (param->read.handle == this->handle_) {
 | 
			
		||||
        this->read_sensors(param->read.value, param->read.value_len);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      this->node_state = espbt::ClientState::ESTABLISHED;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_NOTIFY_EVT: {
 | 
			
		||||
      if (param->notify.conn_id != this->parent()->get_conn_id())
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->notify.handle == this->acp_handle_) {
 | 
			
		||||
        this->read_battery_(param->notify.value, param->notify.value_len);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AirthingsWaveBase::is_valid_voc_value_(uint16_t voc) { return voc <= 16383; }
 | 
			
		||||
 | 
			
		||||
void AirthingsWaveBase::update() {
 | 
			
		||||
  if (this->node_state != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    if (!this->parent()->enabled) {
 | 
			
		||||
      ESP_LOGW(TAG, "Reconnecting to device");
 | 
			
		||||
      this->parent()->set_enabled(true);
 | 
			
		||||
      this->parent()->connect();
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGW(TAG, "Connection in progress");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AirthingsWaveBase::request_read_values_() {
 | 
			
		||||
  auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_);
 | 
			
		||||
  if (chr == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
 | 
			
		||||
             this->sensors_data_characteristic_uuid_.to_string().c_str());
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->handle_ = chr->handle;
 | 
			
		||||
 | 
			
		||||
  auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
 | 
			
		||||
                                        ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->response_pending_();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AirthingsWaveBase::request_battery_() {
 | 
			
		||||
  uint8_t battery_command = ACCESS_CONTROL_POINT_COMMAND;
 | 
			
		||||
  uint8_t cccd_value[2] = {1, 0};
 | 
			
		||||
 | 
			
		||||
  auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->access_control_point_characteristic_uuid_);
 | 
			
		||||
  if (chr == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s",
 | 
			
		||||
             this->service_uuid_.to_string().c_str(),
 | 
			
		||||
             this->access_control_point_characteristic_uuid_.to_string().c_str());
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto *descr = this->parent()->get_descriptor(this->service_uuid_, this->access_control_point_characteristic_uuid_,
 | 
			
		||||
                                               CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID);
 | 
			
		||||
  if (descr == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_string().c_str(),
 | 
			
		||||
             this->access_control_point_characteristic_uuid_.to_string().c_str());
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto reg_status =
 | 
			
		||||
      esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), this->parent()->get_remote_bda(), chr->handle);
 | 
			
		||||
  if (reg_status) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", reg_status);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->acp_handle_ = chr->handle;
 | 
			
		||||
  this->cccd_handle_ = descr->handle;
 | 
			
		||||
 | 
			
		||||
  auto descr_status =
 | 
			
		||||
      esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->cccd_handle_,
 | 
			
		||||
                                     2, cccd_value, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (descr_status) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error sending CCC descriptor write request, status=%d", descr_status);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto chr_status =
 | 
			
		||||
      esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->acp_handle_, 1,
 | 
			
		||||
                               &battery_command, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (chr_status) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error sending read request for battery, status=%d", chr_status);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->response_pending_();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AirthingsWaveBase::read_battery_(uint8_t *raw_value, uint16_t value_len) {
 | 
			
		||||
  auto *value = (AccessControlPointResponse *) (&raw_value[2]);
 | 
			
		||||
 | 
			
		||||
  if ((value_len >= (sizeof(AccessControlPointResponse) + 2)) && (raw_value[0] == ACCESS_CONTROL_POINT_COMMAND)) {
 | 
			
		||||
    ESP_LOGD(TAG, "Battery received: %u mV", (unsigned int) value->battery);
 | 
			
		||||
 | 
			
		||||
    if (this->battery_voltage_ != nullptr) {
 | 
			
		||||
      float voltage = value->battery / 1000.0f;
 | 
			
		||||
 | 
			
		||||
      this->battery_voltage_->publish_state(voltage);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // read the battery again at the configured update interval
 | 
			
		||||
    if (this->battery_update_interval_ != this->update_interval_) {
 | 
			
		||||
      this->read_battery_next_update_ = false;
 | 
			
		||||
      this->set_timeout("battery", this->battery_update_interval_,
 | 
			
		||||
                        [this]() { this->read_battery_next_update_ = true; });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->response_received_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AirthingsWaveBase::response_pending_() {
 | 
			
		||||
  this->responses_pending_++;
 | 
			
		||||
  this->set_response_timeout_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AirthingsWaveBase::response_received_() {
 | 
			
		||||
  if (--this->responses_pending_ == 0) {
 | 
			
		||||
    // This instance must not stay connected
 | 
			
		||||
    // so other clients can connect to it (e.g. the
 | 
			
		||||
    // mobile app).
 | 
			
		||||
    this->parent()->set_enabled(false);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AirthingsWaveBase::set_response_timeout_() {
 | 
			
		||||
  this->set_timeout("response_timeout", 30 * 1000, [this]() {
 | 
			
		||||
    this->responses_pending_ = 1;
 | 
			
		||||
    this->response_received_();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace airthings_wave_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
@@ -1,90 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
// All information related to reading battery levels came from the sensors.airthings_wave
 | 
			
		||||
// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
#include "esphome/components/ble_client/ble_client.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace airthings_wave_base {
 | 
			
		||||
 | 
			
		||||
namespace espbt = esphome::esp32_ble_tracker;
 | 
			
		||||
 | 
			
		||||
static const uint8_t ACCESS_CONTROL_POINT_COMMAND = 0x6d;
 | 
			
		||||
static const auto CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID = espbt::ESPBTUUID::from_uint16(0x2902);
 | 
			
		||||
 | 
			
		||||
class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientNode {
 | 
			
		||||
 public:
 | 
			
		||||
  AirthingsWaveBase() = default;
 | 
			
		||||
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
 | 
			
		||||
  void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
 | 
			
		||||
  void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
 | 
			
		||||
  void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
 | 
			
		||||
  void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
 | 
			
		||||
  void set_battery_voltage(sensor::Sensor *voltage) {
 | 
			
		||||
    battery_voltage_ = voltage;
 | 
			
		||||
    this->read_battery_next_update_ = true;
 | 
			
		||||
  }
 | 
			
		||||
  void set_battery_update_interval(uint32_t interval) { battery_update_interval_ = interval; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool is_valid_voc_value_(uint16_t voc);
 | 
			
		||||
 | 
			
		||||
  bool request_read_values_();
 | 
			
		||||
  virtual void read_sensors(uint8_t *raw_value, uint16_t value_len) = 0;
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *temperature_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *humidity_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *pressure_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *tvoc_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *battery_voltage_{nullptr};
 | 
			
		||||
 | 
			
		||||
  uint16_t handle_;
 | 
			
		||||
  espbt::ESPBTUUID service_uuid_;
 | 
			
		||||
  espbt::ESPBTUUID sensors_data_characteristic_uuid_;
 | 
			
		||||
 | 
			
		||||
  uint16_t acp_handle_{0};
 | 
			
		||||
  uint16_t cccd_handle_{0};
 | 
			
		||||
  espbt::ESPBTUUID access_control_point_characteristic_uuid_;
 | 
			
		||||
 | 
			
		||||
  uint8_t responses_pending_{0};
 | 
			
		||||
  void response_pending_();
 | 
			
		||||
  void response_received_();
 | 
			
		||||
  void set_response_timeout_();
 | 
			
		||||
 | 
			
		||||
  // default to *not* reading battery voltage from the device; the
 | 
			
		||||
  // set_* function for the battery sensor will set this to 'true'
 | 
			
		||||
  bool read_battery_next_update_{false};
 | 
			
		||||
  bool request_battery_();
 | 
			
		||||
  void read_battery_(uint8_t *raw_value, uint16_t value_len);
 | 
			
		||||
  uint32_t battery_update_interval_{};
 | 
			
		||||
 | 
			
		||||
  struct AccessControlPointResponse {
 | 
			
		||||
    uint32_t unused1;
 | 
			
		||||
    uint8_t unused2;
 | 
			
		||||
    uint8_t illuminance;
 | 
			
		||||
    uint8_t unused3[10];
 | 
			
		||||
    uint16_t unused4[4];
 | 
			
		||||
    uint16_t battery;
 | 
			
		||||
    uint16_t unused5;
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace airthings_wave_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
@@ -7,47 +7,105 @@ namespace airthings_wave_mini {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "airthings_wave_mini";
 | 
			
		||||
 | 
			
		||||
void AirthingsWaveMini::read_sensors(uint8_t *raw_value, uint16_t value_len) {
 | 
			
		||||
void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                            esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      if (param->open.status == ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGI(TAG, "Connected successfully!");
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
      ESP_LOGW(TAG, "Disconnected!");
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
      this->handle_ = 0;
 | 
			
		||||
      auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
 | 
			
		||||
      if (chr == nullptr) {
 | 
			
		||||
        ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
 | 
			
		||||
                 sensors_data_characteristic_uuid_.to_string().c_str());
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      this->handle_ = chr->handle;
 | 
			
		||||
      this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
 | 
			
		||||
 | 
			
		||||
      request_read_values_();
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_READ_CHAR_EVT: {
 | 
			
		||||
      if (param->read.conn_id != this->parent()->get_conn_id())
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->read.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      if (param->read.handle == this->handle_) {
 | 
			
		||||
        read_sensors_(param->read.value, param->read.value_len);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
 | 
			
		||||
  auto *value = (WaveMiniReadings *) raw_value;
 | 
			
		||||
 | 
			
		||||
  if (sizeof(WaveMiniReadings) <= value_len) {
 | 
			
		||||
    if (this->humidity_sensor_ != nullptr) {
 | 
			
		||||
      this->humidity_sensor_->publish_state(value->humidity / 100.0f);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this->pressure_sensor_ != nullptr) {
 | 
			
		||||
      this->pressure_sensor_->publish_state(value->pressure / 50.0f);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this->temperature_sensor_ != nullptr) {
 | 
			
		||||
      this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
 | 
			
		||||
    this->humidity_sensor_->publish_state(value->humidity / 100.0f);
 | 
			
		||||
    this->pressure_sensor_->publish_state(value->pressure / 50.0f);
 | 
			
		||||
    this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f);
 | 
			
		||||
    if (is_valid_voc_value_(value->voc)) {
 | 
			
		||||
      this->tvoc_sensor_->publish_state(value->voc);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->response_received_();
 | 
			
		||||
    // This instance must not stay connected
 | 
			
		||||
    // so other clients can connect to it (e.g. the
 | 
			
		||||
    // mobile app).
 | 
			
		||||
    parent()->set_enabled(false);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AirthingsWaveMini::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
 | 
			
		||||
 | 
			
		||||
void AirthingsWaveMini::update() {
 | 
			
		||||
  if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
 | 
			
		||||
    if (!parent()->enabled) {
 | 
			
		||||
      ESP_LOGW(TAG, "Reconnecting to device");
 | 
			
		||||
      parent()->set_enabled(true);
 | 
			
		||||
      parent()->connect();
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGW(TAG, "Connection in progress");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AirthingsWaveMini::request_read_values_() {
 | 
			
		||||
  auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
 | 
			
		||||
                                        ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AirthingsWaveMini::dump_config() {
 | 
			
		||||
  // these really don't belong here, but there doesn't seem to be a
 | 
			
		||||
  // practical way to have the base class use LOG_SENSOR and include
 | 
			
		||||
  // the TAG from this component
 | 
			
		||||
  LOG_SENSOR("  ", "Humidity", this->humidity_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Temperature", this->temperature_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Pressure", this->pressure_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "TVOC", this->tvoc_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Battery Voltage", this->battery_voltage_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AirthingsWaveMini::AirthingsWaveMini() {
 | 
			
		||||
  this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID);
 | 
			
		||||
  this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
 | 
			
		||||
  this->access_control_point_characteristic_uuid_ =
 | 
			
		||||
      espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID);
 | 
			
		||||
}
 | 
			
		||||
AirthingsWaveMini::AirthingsWaveMini()
 | 
			
		||||
    : PollingComponent(10000),
 | 
			
		||||
      service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
 | 
			
		||||
      sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
 | 
			
		||||
 | 
			
		||||
}  // namespace airthings_wave_mini
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -2,25 +2,50 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/airthings_wave_base/airthings_wave_base.h"
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
#include "esphome/components/ble_client/ble_client.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace airthings_wave_mini {
 | 
			
		||||
 | 
			
		||||
namespace espbt = esphome::esp32_ble_tracker;
 | 
			
		||||
 | 
			
		||||
static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba";
 | 
			
		||||
static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba";
 | 
			
		||||
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e3ef4-ade7-11e4-89d3-123b93f75cba";
 | 
			
		||||
 | 
			
		||||
class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase {
 | 
			
		||||
class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode {
 | 
			
		||||
 public:
 | 
			
		||||
  AirthingsWaveMini();
 | 
			
		||||
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
 | 
			
		||||
  void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
 | 
			
		||||
  void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
 | 
			
		||||
  void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
 | 
			
		||||
  void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
 | 
			
		||||
  bool is_valid_voc_value_(uint16_t voc);
 | 
			
		||||
 | 
			
		||||
  void read_sensors_(uint8_t *value, uint16_t value_len);
 | 
			
		||||
  void request_read_values_();
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *temperature_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *humidity_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *pressure_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *tvoc_sensor_{nullptr};
 | 
			
		||||
 | 
			
		||||
  uint16_t handle_;
 | 
			
		||||
  esp32_ble_tracker::ESPBTUUID service_uuid_;
 | 
			
		||||
  esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
 | 
			
		||||
 | 
			
		||||
  struct WaveMiniReadings {
 | 
			
		||||
    uint16_t unused01;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,82 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import airthings_wave_base
 | 
			
		||||
from esphome.components import sensor, ble_client
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_PRESSURE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_HECTOPASCAL,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_TVOC,
 | 
			
		||||
    CONF_PRESSURE,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    UNIT_PARTS_PER_BILLION,
 | 
			
		||||
    ICON_RADIATOR,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["airthings_wave_base"]
 | 
			
		||||
DEPENDENCIES = ["ble_client"]
 | 
			
		||||
 | 
			
		||||
airthings_wave_mini_ns = cg.esphome_ns.namespace("airthings_wave_mini")
 | 
			
		||||
AirthingsWaveMini = airthings_wave_mini_ns.class_(
 | 
			
		||||
    "AirthingsWaveMini", airthings_wave_base.AirthingsWaveBase
 | 
			
		||||
    "AirthingsWaveMini", cg.PollingComponent, ble_client.BLEClientNode
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(AirthingsWaveMini),
 | 
			
		||||
    }
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(AirthingsWaveMini),
 | 
			
		||||
            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_PERCENT,
 | 
			
		||||
                device_class=DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_CELSIUS,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_HECTOPASCAL,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
                device_class=DEVICE_CLASS_PRESSURE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TVOC): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_PARTS_PER_BILLION,
 | 
			
		||||
                icon=ICON_RADIATOR,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("5min"))
 | 
			
		||||
    .extend(ble_client.BLE_CLIENT_SCHEMA),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await airthings_wave_base.wave_base_to_code(var, config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
 | 
			
		||||
    if CONF_HUMIDITY in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_HUMIDITY])
 | 
			
		||||
        cg.add(var.set_humidity(sens))
 | 
			
		||||
    if CONF_TEMPERATURE in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
 | 
			
		||||
        cg.add(var.set_temperature(sens))
 | 
			
		||||
    if CONF_PRESSURE in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_PRESSURE])
 | 
			
		||||
        cg.add(var.set_pressure(sens))
 | 
			
		||||
    if CONF_TVOC in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_TVOC])
 | 
			
		||||
        cg.add(var.set_tvoc(sens))
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,55 @@ namespace airthings_wave_plus {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "airthings_wave_plus";
 | 
			
		||||
 | 
			
		||||
void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
 | 
			
		||||
void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                            esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      if (param->open.status == ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGI(TAG, "Connected successfully!");
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
      ESP_LOGW(TAG, "Disconnected!");
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
      this->handle_ = 0;
 | 
			
		||||
      auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
 | 
			
		||||
      if (chr == nullptr) {
 | 
			
		||||
        ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
 | 
			
		||||
                 sensors_data_characteristic_uuid_.to_string().c_str());
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      this->handle_ = chr->handle;
 | 
			
		||||
      this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
 | 
			
		||||
 | 
			
		||||
      request_read_values_();
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_READ_CHAR_EVT: {
 | 
			
		||||
      if (param->read.conn_id != this->parent()->get_conn_id())
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->read.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      if (param->read.handle == this->handle_) {
 | 
			
		||||
        read_sensors_(param->read.value, param->read.value_len);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
 | 
			
		||||
  auto *value = (WavePlusReadings *) raw_value;
 | 
			
		||||
 | 
			
		||||
  if (sizeof(WavePlusReadings) <= value_len) {
 | 
			
		||||
@@ -16,66 +64,72 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
 | 
			
		||||
    if (value->version == 1) {
 | 
			
		||||
      ESP_LOGD(TAG, "ambient light = %d", value->ambientLight);
 | 
			
		||||
 | 
			
		||||
      if (this->humidity_sensor_ != nullptr) {
 | 
			
		||||
        this->humidity_sensor_->publish_state(value->humidity / 2.0f);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ((this->radon_sensor_ != nullptr) && this->is_valid_radon_value_(value->radon)) {
 | 
			
		||||
      this->humidity_sensor_->publish_state(value->humidity / 2.0f);
 | 
			
		||||
      if (is_valid_radon_value_(value->radon)) {
 | 
			
		||||
        this->radon_sensor_->publish_state(value->radon);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ((this->radon_long_term_sensor_ != nullptr) && this->is_valid_radon_value_(value->radon_lt)) {
 | 
			
		||||
      if (is_valid_radon_value_(value->radon_lt)) {
 | 
			
		||||
        this->radon_long_term_sensor_->publish_state(value->radon_lt);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this->temperature_sensor_ != nullptr) {
 | 
			
		||||
        this->temperature_sensor_->publish_state(value->temperature / 100.0f);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this->pressure_sensor_ != nullptr) {
 | 
			
		||||
        this->pressure_sensor_->publish_state(value->pressure / 50.0f);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ((this->co2_sensor_ != nullptr) && this->is_valid_co2_value_(value->co2)) {
 | 
			
		||||
      this->temperature_sensor_->publish_state(value->temperature / 100.0f);
 | 
			
		||||
      this->pressure_sensor_->publish_state(value->pressure / 50.0f);
 | 
			
		||||
      if (is_valid_co2_value_(value->co2)) {
 | 
			
		||||
        this->co2_sensor_->publish_state(value->co2);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
 | 
			
		||||
      if (is_valid_voc_value_(value->voc)) {
 | 
			
		||||
        this->tvoc_sensor_->publish_state(value->voc);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // This instance must not stay connected
 | 
			
		||||
      // so other clients can connect to it (e.g. the
 | 
			
		||||
      // mobile app).
 | 
			
		||||
      parent()->set_enabled(false);
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->response_received_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return radon <= 16383; }
 | 
			
		||||
bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; }
 | 
			
		||||
 | 
			
		||||
bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return co2 <= 16383; }
 | 
			
		||||
bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
 | 
			
		||||
 | 
			
		||||
bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; }
 | 
			
		||||
 | 
			
		||||
void AirthingsWavePlus::update() {
 | 
			
		||||
  if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
 | 
			
		||||
    if (!parent()->enabled) {
 | 
			
		||||
      ESP_LOGW(TAG, "Reconnecting to device");
 | 
			
		||||
      parent()->set_enabled(true);
 | 
			
		||||
      parent()->connect();
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGW(TAG, "Connection in progress");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AirthingsWavePlus::request_read_values_() {
 | 
			
		||||
  auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
 | 
			
		||||
                                        ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AirthingsWavePlus::dump_config() {
 | 
			
		||||
  // these really don't belong here, but there doesn't seem to be a
 | 
			
		||||
  // practical way to have the base class use LOG_SENSOR and include
 | 
			
		||||
  // the TAG from this component
 | 
			
		||||
  LOG_SENSOR("  ", "Humidity", this->humidity_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Temperature", this->temperature_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Pressure", this->pressure_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "TVOC", this->tvoc_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Battery Voltage", this->battery_voltage_);
 | 
			
		||||
 | 
			
		||||
  LOG_SENSOR("  ", "Radon", this->radon_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Radon Long Term", this->radon_long_term_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Temperature", this->temperature_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Pressure", this->pressure_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "CO2", this->co2_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "TVOC", this->tvoc_sensor_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AirthingsWavePlus::AirthingsWavePlus() {
 | 
			
		||||
  this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID);
 | 
			
		||||
  this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
 | 
			
		||||
  this->access_control_point_characteristic_uuid_ =
 | 
			
		||||
      espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID);
 | 
			
		||||
}
 | 
			
		||||
AirthingsWavePlus::AirthingsWavePlus()
 | 
			
		||||
    : PollingComponent(10000),
 | 
			
		||||
      service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
 | 
			
		||||
      sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
 | 
			
		||||
 | 
			
		||||
}  // namespace airthings_wave_plus
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -2,36 +2,58 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/airthings_wave_base/airthings_wave_base.h"
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
#include "esphome/components/ble_client/ble_client.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace airthings_wave_plus {
 | 
			
		||||
 | 
			
		||||
namespace espbt = esphome::esp32_ble_tracker;
 | 
			
		||||
 | 
			
		||||
static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba";
 | 
			
		||||
static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
 | 
			
		||||
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e2d06-ade7-11e4-89d3-123b93f75cba";
 | 
			
		||||
 | 
			
		||||
class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
 | 
			
		||||
class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientNode {
 | 
			
		||||
 public:
 | 
			
		||||
  AirthingsWavePlus();
 | 
			
		||||
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
 | 
			
		||||
  void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
 | 
			
		||||
  void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
 | 
			
		||||
  void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
 | 
			
		||||
  void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
 | 
			
		||||
  void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
 | 
			
		||||
  void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
 | 
			
		||||
  void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool is_valid_radon_value_(uint16_t radon);
 | 
			
		||||
  bool is_valid_voc_value_(uint16_t voc);
 | 
			
		||||
  bool is_valid_co2_value_(uint16_t co2);
 | 
			
		||||
 | 
			
		||||
  void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
 | 
			
		||||
  void read_sensors_(uint8_t *value, uint16_t value_len);
 | 
			
		||||
  void request_read_values_();
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *temperature_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *radon_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *radon_long_term_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *humidity_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *pressure_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *co2_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *tvoc_sensor_{nullptr};
 | 
			
		||||
 | 
			
		||||
  uint16_t handle_;
 | 
			
		||||
  esp32_ble_tracker::ESPBTUUID service_uuid_;
 | 
			
		||||
  esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
 | 
			
		||||
 | 
			
		||||
  struct WavePlusReadings {
 | 
			
		||||
    uint8_t version;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,64 +1,116 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, airthings_wave_base
 | 
			
		||||
from esphome.components import sensor, ble_client
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_PRESSURE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_HECTOPASCAL,
 | 
			
		||||
    ICON_RADIOACTIVE,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_RADON,
 | 
			
		||||
    CONF_RADON_LONG_TERM,
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_TVOC,
 | 
			
		||||
    CONF_CO2,
 | 
			
		||||
    CONF_PRESSURE,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    UNIT_BECQUEREL_PER_CUBIC_METER,
 | 
			
		||||
    UNIT_PARTS_PER_MILLION,
 | 
			
		||||
    UNIT_PARTS_PER_BILLION,
 | 
			
		||||
    ICON_RADIATOR,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["airthings_wave_base"]
 | 
			
		||||
DEPENDENCIES = ["ble_client"]
 | 
			
		||||
 | 
			
		||||
airthings_wave_plus_ns = cg.esphome_ns.namespace("airthings_wave_plus")
 | 
			
		||||
AirthingsWavePlus = airthings_wave_plus_ns.class_(
 | 
			
		||||
    "AirthingsWavePlus", airthings_wave_base.AirthingsWaveBase
 | 
			
		||||
    "AirthingsWavePlus", cg.PollingComponent, ble_client.BLEClientNode
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
 | 
			
		||||
        cv.Optional(CONF_RADON): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
 | 
			
		||||
            icon=ICON_RADIOACTIVE,
 | 
			
		||||
            accuracy_decimals=0,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
 | 
			
		||||
            icon=ICON_RADIOACTIVE,
 | 
			
		||||
            accuracy_decimals=0,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_CO2): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_PARTS_PER_MILLION,
 | 
			
		||||
            accuracy_decimals=0,
 | 
			
		||||
            device_class=DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
 | 
			
		||||
            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_PERCENT,
 | 
			
		||||
                device_class=DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_RADON): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
 | 
			
		||||
                icon=ICON_RADIOACTIVE,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
 | 
			
		||||
                icon=ICON_RADIOACTIVE,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_CELSIUS,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_HECTOPASCAL,
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                device_class=DEVICE_CLASS_PRESSURE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CO2): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_PARTS_PER_MILLION,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                device_class=DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TVOC): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_PARTS_PER_BILLION,
 | 
			
		||||
                icon=ICON_RADIATOR,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("5min"))
 | 
			
		||||
    .extend(ble_client.BLE_CLIENT_SCHEMA),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await airthings_wave_base.wave_base_to_code(var, config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    if config_radon := config.get(CONF_RADON):
 | 
			
		||||
        sens = await sensor.new_sensor(config_radon)
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
 | 
			
		||||
    if CONF_HUMIDITY in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_HUMIDITY])
 | 
			
		||||
        cg.add(var.set_humidity(sens))
 | 
			
		||||
    if CONF_RADON in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_RADON])
 | 
			
		||||
        cg.add(var.set_radon(sens))
 | 
			
		||||
    if config_radon_long_term := config.get(CONF_RADON_LONG_TERM):
 | 
			
		||||
        sens = await sensor.new_sensor(config_radon_long_term)
 | 
			
		||||
    if CONF_RADON_LONG_TERM in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM])
 | 
			
		||||
        cg.add(var.set_radon_long_term(sens))
 | 
			
		||||
    if config_co2 := config.get(CONF_CO2):
 | 
			
		||||
        sens = await sensor.new_sensor(config_co2)
 | 
			
		||||
    if CONF_TEMPERATURE in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
 | 
			
		||||
        cg.add(var.set_temperature(sens))
 | 
			
		||||
    if CONF_PRESSURE in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_PRESSURE])
 | 
			
		||||
        cg.add(var.set_pressure(sens))
 | 
			
		||||
    if CONF_CO2 in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_CO2])
 | 
			
		||||
        cg.add(var.set_co2(sens))
 | 
			
		||||
    if CONF_TVOC in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_TVOC])
 | 
			
		||||
        cg.add(var.set_tvoc(sens))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,165 +0,0 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import maybe_simple_id
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_ON_STATE,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_CODE,
 | 
			
		||||
)
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@grahambrown11"]
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
CONF_ON_TRIGGERED = "on_triggered"
 | 
			
		||||
CONF_ON_CLEARED = "on_cleared"
 | 
			
		||||
 | 
			
		||||
alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel")
 | 
			
		||||
AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase)
 | 
			
		||||
 | 
			
		||||
StateTrigger = alarm_control_panel_ns.class_(
 | 
			
		||||
    "StateTrigger", automation.Trigger.template()
 | 
			
		||||
)
 | 
			
		||||
TriggeredTrigger = alarm_control_panel_ns.class_(
 | 
			
		||||
    "TriggeredTrigger", automation.Trigger.template()
 | 
			
		||||
)
 | 
			
		||||
ClearedTrigger = alarm_control_panel_ns.class_(
 | 
			
		||||
    "ClearedTrigger", automation.Trigger.template()
 | 
			
		||||
)
 | 
			
		||||
ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action)
 | 
			
		||||
ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action)
 | 
			
		||||
DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action)
 | 
			
		||||
PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action)
 | 
			
		||||
TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action)
 | 
			
		||||
AlarmControlPanelCondition = alarm_control_panel_ns.class_(
 | 
			
		||||
    "AlarmControlPanelCondition", automation.Condition
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(AlarmControlPanel),
 | 
			
		||||
        cv.Optional(CONF_ON_STATE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(AlarmControlPanel),
 | 
			
		||||
        cv.Optional(CONF_CODE): cv.templatable(cv.string),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(AlarmControlPanel),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_alarm_control_panel_core_(var, config):
 | 
			
		||||
    await setup_entity(var, config)
 | 
			
		||||
    for conf in config.get(CONF_ON_STATE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_TRIGGERED, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_CLEARED, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def register_alarm_control_panel(var, config):
 | 
			
		||||
    if not CORE.has_id(config[CONF_ID]):
 | 
			
		||||
        var = cg.Pvariable(config[CONF_ID], var)
 | 
			
		||||
    cg.add(cg.App.register_alarm_control_panel(var))
 | 
			
		||||
    await setup_alarm_control_panel_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "alarm_control_panel.arm_away", ArmAwayAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
async def alarm_action_arm_away_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
    if CONF_CODE in config:
 | 
			
		||||
        templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
 | 
			
		||||
        cg.add(var.set_code(templatable_))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "alarm_control_panel.arm_home", ArmHomeAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
async def alarm_action_arm_home_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
    if CONF_CODE in config:
 | 
			
		||||
        templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
 | 
			
		||||
        cg.add(var.set_code(templatable_))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "alarm_control_panel.disarm", DisarmAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
async def alarm_action_disarm_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
    if CONF_CODE in config:
 | 
			
		||||
        templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
 | 
			
		||||
        cg.add(var.set_code(templatable_))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "alarm_control_panel.pending", PendingAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
async def alarm_action_pending_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "alarm_control_panel.triggered", TriggeredAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
async def alarm_action_trigger_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_condition(
 | 
			
		||||
    "alarm_control_panel.is_armed",
 | 
			
		||||
    AlarmControlPanelCondition,
 | 
			
		||||
    ALARM_CONTROL_PANEL_CONDITION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
async def alarm_control_panel_is_armed_to_code(
 | 
			
		||||
    config, condition_id, template_arg, args
 | 
			
		||||
):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(condition_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(100.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add_global(alarm_control_panel_ns.using)
 | 
			
		||||
    cg.add_define("USE_ALARM_CONTROL_PANEL")
 | 
			
		||||
@@ -1,111 +0,0 @@
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include "alarm_control_panel.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace alarm_control_panel {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "alarm_control_panel";
 | 
			
		||||
 | 
			
		||||
AlarmControlPanelCall AlarmControlPanel::make_call() { return AlarmControlPanelCall(this); }
 | 
			
		||||
 | 
			
		||||
bool AlarmControlPanel::is_state_armed(AlarmControlPanelState state) {
 | 
			
		||||
  switch (state) {
 | 
			
		||||
    case ACP_STATE_ARMED_AWAY:
 | 
			
		||||
    case ACP_STATE_ARMED_HOME:
 | 
			
		||||
    case ACP_STATE_ARMED_NIGHT:
 | 
			
		||||
    case ACP_STATE_ARMED_VACATION:
 | 
			
		||||
    case ACP_STATE_ARMED_CUSTOM_BYPASS:
 | 
			
		||||
      return true;
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
 | 
			
		||||
  this->last_update_ = millis();
 | 
			
		||||
  if (state != this->current_state_) {
 | 
			
		||||
    auto prev_state = this->current_state_;
 | 
			
		||||
    ESP_LOGD(TAG, "Set state to: %s, previous: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
 | 
			
		||||
             LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
 | 
			
		||||
    this->current_state_ = state;
 | 
			
		||||
    this->state_callback_.call();
 | 
			
		||||
    if (state == ACP_STATE_TRIGGERED) {
 | 
			
		||||
      this->triggered_callback_.call();
 | 
			
		||||
    }
 | 
			
		||||
    if (prev_state == ACP_STATE_TRIGGERED) {
 | 
			
		||||
      this->cleared_callback_.call();
 | 
			
		||||
    }
 | 
			
		||||
    if (state == this->desired_state_) {
 | 
			
		||||
      // only store when in the desired state
 | 
			
		||||
      this->pref_.save(&state);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AlarmControlPanel::add_on_state_callback(std::function<void()> &&callback) {
 | 
			
		||||
  this->state_callback_.add(std::move(callback));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AlarmControlPanel::add_on_triggered_callback(std::function<void()> &&callback) {
 | 
			
		||||
  this->triggered_callback_.add(std::move(callback));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) {
 | 
			
		||||
  this->cleared_callback_.add(std::move(callback));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AlarmControlPanel::arm_away(optional<std::string> code) {
 | 
			
		||||
  auto call = this->make_call();
 | 
			
		||||
  call.arm_away();
 | 
			
		||||
  if (code.has_value())
 | 
			
		||||
    call.set_code(code.value());
 | 
			
		||||
  call.perform();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AlarmControlPanel::arm_home(optional<std::string> code) {
 | 
			
		||||
  auto call = this->make_call();
 | 
			
		||||
  call.arm_home();
 | 
			
		||||
  if (code.has_value())
 | 
			
		||||
    call.set_code(code.value());
 | 
			
		||||
  call.perform();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AlarmControlPanel::arm_night(optional<std::string> code) {
 | 
			
		||||
  auto call = this->make_call();
 | 
			
		||||
  call.arm_night();
 | 
			
		||||
  if (code.has_value())
 | 
			
		||||
    call.set_code(code.value());
 | 
			
		||||
  call.perform();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AlarmControlPanel::arm_vacation(optional<std::string> code) {
 | 
			
		||||
  auto call = this->make_call();
 | 
			
		||||
  call.arm_vacation();
 | 
			
		||||
  if (code.has_value())
 | 
			
		||||
    call.set_code(code.value());
 | 
			
		||||
  call.perform();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AlarmControlPanel::arm_custom_bypass(optional<std::string> code) {
 | 
			
		||||
  auto call = this->make_call();
 | 
			
		||||
  call.arm_custom_bypass();
 | 
			
		||||
  if (code.has_value())
 | 
			
		||||
    call.set_code(code.value());
 | 
			
		||||
  call.perform();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AlarmControlPanel::disarm(optional<std::string> code) {
 | 
			
		||||
  auto call = this->make_call();
 | 
			
		||||
  call.disarm();
 | 
			
		||||
  if (code.has_value())
 | 
			
		||||
    call.set_code(code.value());
 | 
			
		||||
  call.perform();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace alarm_control_panel
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,136 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
 | 
			
		||||
#include "alarm_control_panel_call.h"
 | 
			
		||||
#include "alarm_control_panel_state.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/entity_base.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace alarm_control_panel {
 | 
			
		||||
 | 
			
		||||
enum AlarmControlPanelFeature : uint8_t {
 | 
			
		||||
  // Matches Home Assistant values
 | 
			
		||||
  ACP_FEAT_ARM_HOME = 1 << 0,
 | 
			
		||||
  ACP_FEAT_ARM_AWAY = 1 << 1,
 | 
			
		||||
  ACP_FEAT_ARM_NIGHT = 1 << 2,
 | 
			
		||||
  ACP_FEAT_TRIGGER = 1 << 3,
 | 
			
		||||
  ACP_FEAT_ARM_CUSTOM_BYPASS = 1 << 4,
 | 
			
		||||
  ACP_FEAT_ARM_VACATION = 1 << 5,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AlarmControlPanel : public EntityBase {
 | 
			
		||||
 public:
 | 
			
		||||
  /** Make a AlarmControlPanelCall
 | 
			
		||||
   *
 | 
			
		||||
   */
 | 
			
		||||
  AlarmControlPanelCall make_call();
 | 
			
		||||
 | 
			
		||||
  /** Set the state of the alarm_control_panel.
 | 
			
		||||
   *
 | 
			
		||||
   * @param state The AlarmControlPanelState.
 | 
			
		||||
   */
 | 
			
		||||
  void publish_state(AlarmControlPanelState state);
 | 
			
		||||
 | 
			
		||||
  /** Add a callback for when the state of the alarm_control_panel changes
 | 
			
		||||
   *
 | 
			
		||||
   * @param callback The callback function
 | 
			
		||||
   */
 | 
			
		||||
  void add_on_state_callback(std::function<void()> &&callback);
 | 
			
		||||
 | 
			
		||||
  /** Add a callback for when the state of the alarm_control_panel chanes to triggered
 | 
			
		||||
   *
 | 
			
		||||
   * @param callback The callback function
 | 
			
		||||
   */
 | 
			
		||||
  void add_on_triggered_callback(std::function<void()> &&callback);
 | 
			
		||||
 | 
			
		||||
  /** Add a callback for when the state of the alarm_control_panel clears from triggered
 | 
			
		||||
   *
 | 
			
		||||
   * @param callback The callback function
 | 
			
		||||
   */
 | 
			
		||||
  void add_on_cleared_callback(std::function<void()> &&callback);
 | 
			
		||||
 | 
			
		||||
  /** A numeric representation of the supported features as per HomeAssistant
 | 
			
		||||
   *
 | 
			
		||||
   */
 | 
			
		||||
  virtual uint32_t get_supported_features() const = 0;
 | 
			
		||||
 | 
			
		||||
  /** Returns if the alarm_control_panel has a code
 | 
			
		||||
   *
 | 
			
		||||
   */
 | 
			
		||||
  virtual bool get_requires_code() const = 0;
 | 
			
		||||
 | 
			
		||||
  /** Returns if the alarm_control_panel requires a code to arm
 | 
			
		||||
   *
 | 
			
		||||
   */
 | 
			
		||||
  virtual bool get_requires_code_to_arm() const = 0;
 | 
			
		||||
 | 
			
		||||
  /** arm the alarm in away mode
 | 
			
		||||
   *
 | 
			
		||||
   * @param code The code
 | 
			
		||||
   */
 | 
			
		||||
  void arm_away(optional<std::string> code = nullopt);
 | 
			
		||||
 | 
			
		||||
  /** arm the alarm in home mode
 | 
			
		||||
   *
 | 
			
		||||
   * @param code The code
 | 
			
		||||
   */
 | 
			
		||||
  void arm_home(optional<std::string> code = nullopt);
 | 
			
		||||
 | 
			
		||||
  /** arm the alarm in night mode
 | 
			
		||||
   *
 | 
			
		||||
   * @param code The code
 | 
			
		||||
   */
 | 
			
		||||
  void arm_night(optional<std::string> code = nullopt);
 | 
			
		||||
 | 
			
		||||
  /** arm the alarm in vacation mode
 | 
			
		||||
   *
 | 
			
		||||
   * @param code The code
 | 
			
		||||
   */
 | 
			
		||||
  void arm_vacation(optional<std::string> code = nullopt);
 | 
			
		||||
 | 
			
		||||
  /** arm the alarm in custom bypass mode
 | 
			
		||||
   *
 | 
			
		||||
   * @param code The code
 | 
			
		||||
   */
 | 
			
		||||
  void arm_custom_bypass(optional<std::string> code = nullopt);
 | 
			
		||||
 | 
			
		||||
  /** disarm the alarm
 | 
			
		||||
   *
 | 
			
		||||
   * @param code The code
 | 
			
		||||
   */
 | 
			
		||||
  void disarm(optional<std::string> code = nullopt);
 | 
			
		||||
 | 
			
		||||
  /** Get the state
 | 
			
		||||
   *
 | 
			
		||||
   */
 | 
			
		||||
  AlarmControlPanelState get_state() const { return this->current_state_; }
 | 
			
		||||
 | 
			
		||||
  // is the state one of the armed states
 | 
			
		||||
  bool is_state_armed(AlarmControlPanelState state);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  friend AlarmControlPanelCall;
 | 
			
		||||
  // in order to store last panel state in flash
 | 
			
		||||
  ESPPreferenceObject pref_;
 | 
			
		||||
  // current state
 | 
			
		||||
  AlarmControlPanelState current_state_;
 | 
			
		||||
  // the desired (or previous) state
 | 
			
		||||
  AlarmControlPanelState desired_state_;
 | 
			
		||||
  // last time the state was updated
 | 
			
		||||
  uint32_t last_update_;
 | 
			
		||||
  // the call control function
 | 
			
		||||
  virtual void control(const AlarmControlPanelCall &call) = 0;
 | 
			
		||||
  // state callback
 | 
			
		||||
  CallbackManager<void()> state_callback_{};
 | 
			
		||||
  // trigger callback
 | 
			
		||||
  CallbackManager<void()> triggered_callback_{};
 | 
			
		||||
  // clear callback
 | 
			
		||||
  CallbackManager<void()> cleared_callback_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace alarm_control_panel
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,99 +0,0 @@
 | 
			
		||||
#include "alarm_control_panel_call.h"
 | 
			
		||||
 | 
			
		||||
#include "alarm_control_panel.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace alarm_control_panel {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "alarm_control_panel";
 | 
			
		||||
 | 
			
		||||
AlarmControlPanelCall::AlarmControlPanelCall(AlarmControlPanel *parent) : parent_(parent) {}
 | 
			
		||||
 | 
			
		||||
AlarmControlPanelCall &AlarmControlPanelCall::set_code(const std::string &code) {
 | 
			
		||||
  this->code_ = code;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AlarmControlPanelCall &AlarmControlPanelCall::arm_away() {
 | 
			
		||||
  this->state_ = ACP_STATE_ARMED_AWAY;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AlarmControlPanelCall &AlarmControlPanelCall::arm_home() {
 | 
			
		||||
  this->state_ = ACP_STATE_ARMED_HOME;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AlarmControlPanelCall &AlarmControlPanelCall::arm_night() {
 | 
			
		||||
  this->state_ = ACP_STATE_ARMED_NIGHT;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AlarmControlPanelCall &AlarmControlPanelCall::arm_vacation() {
 | 
			
		||||
  this->state_ = ACP_STATE_ARMED_VACATION;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AlarmControlPanelCall &AlarmControlPanelCall::arm_custom_bypass() {
 | 
			
		||||
  this->state_ = ACP_STATE_ARMED_CUSTOM_BYPASS;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AlarmControlPanelCall &AlarmControlPanelCall::disarm() {
 | 
			
		||||
  this->state_ = ACP_STATE_DISARMED;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AlarmControlPanelCall &AlarmControlPanelCall::pending() {
 | 
			
		||||
  this->state_ = ACP_STATE_PENDING;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AlarmControlPanelCall &AlarmControlPanelCall::triggered() {
 | 
			
		||||
  this->state_ = ACP_STATE_TRIGGERED;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const optional<AlarmControlPanelState> &AlarmControlPanelCall::get_state() const { return this->state_; }
 | 
			
		||||
const optional<std::string> &AlarmControlPanelCall::get_code() const { return this->code_; }
 | 
			
		||||
 | 
			
		||||
void AlarmControlPanelCall::validate_() {
 | 
			
		||||
  if (this->state_.has_value()) {
 | 
			
		||||
    auto state = *this->state_;
 | 
			
		||||
    if (this->parent_->is_state_armed(state) && this->parent_->get_state() != ACP_STATE_DISARMED) {
 | 
			
		||||
      ESP_LOGW(TAG, "Cannot arm when not disarmed");
 | 
			
		||||
      this->state_.reset();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (state == ACP_STATE_PENDING && this->parent_->get_state() == ACP_STATE_DISARMED) {
 | 
			
		||||
      ESP_LOGW(TAG, "Cannot trip alarm when disarmed");
 | 
			
		||||
      this->state_.reset();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (state == ACP_STATE_DISARMED &&
 | 
			
		||||
        !(this->parent_->is_state_armed(this->parent_->get_state()) ||
 | 
			
		||||
          this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_ARMING ||
 | 
			
		||||
          this->parent_->get_state() == ACP_STATE_TRIGGERED)) {
 | 
			
		||||
      ESP_LOGW(TAG, "Cannot disarm when not armed");
 | 
			
		||||
      this->state_.reset();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (state == ACP_STATE_ARMED_HOME && (this->parent_->get_supported_features() & ACP_FEAT_ARM_HOME) == 0) {
 | 
			
		||||
      ESP_LOGW(TAG, "Cannot arm home when not supported");
 | 
			
		||||
      this->state_.reset();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AlarmControlPanelCall::perform() {
 | 
			
		||||
  this->validate_();
 | 
			
		||||
  if (this->state_) {
 | 
			
		||||
    this->parent_->control(*this);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace alarm_control_panel
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,40 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include "alarm_control_panel_state.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace alarm_control_panel {
 | 
			
		||||
 | 
			
		||||
class AlarmControlPanel;
 | 
			
		||||
 | 
			
		||||
class AlarmControlPanelCall {
 | 
			
		||||
 public:
 | 
			
		||||
  AlarmControlPanelCall(AlarmControlPanel *parent);
 | 
			
		||||
 | 
			
		||||
  AlarmControlPanelCall &set_code(const std::string &code);
 | 
			
		||||
  AlarmControlPanelCall &arm_away();
 | 
			
		||||
  AlarmControlPanelCall &arm_home();
 | 
			
		||||
  AlarmControlPanelCall &arm_night();
 | 
			
		||||
  AlarmControlPanelCall &arm_vacation();
 | 
			
		||||
  AlarmControlPanelCall &arm_custom_bypass();
 | 
			
		||||
  AlarmControlPanelCall &disarm();
 | 
			
		||||
  AlarmControlPanelCall &pending();
 | 
			
		||||
  AlarmControlPanelCall &triggered();
 | 
			
		||||
 | 
			
		||||
  void perform();
 | 
			
		||||
  const optional<AlarmControlPanelState> &get_state() const;
 | 
			
		||||
  const optional<std::string> &get_code() const;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  AlarmControlPanel *parent_;
 | 
			
		||||
  optional<std::string> code_{};
 | 
			
		||||
  optional<AlarmControlPanelState> state_{};
 | 
			
		||||
  void validate_();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace alarm_control_panel
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
#include "alarm_control_panel_state.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace alarm_control_panel {
 | 
			
		||||
 | 
			
		||||
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) {
 | 
			
		||||
  switch (state) {
 | 
			
		||||
    case ACP_STATE_DISARMED:
 | 
			
		||||
      return LOG_STR("DISARMED");
 | 
			
		||||
    case ACP_STATE_ARMED_HOME:
 | 
			
		||||
      return LOG_STR("ARMED_HOME");
 | 
			
		||||
    case ACP_STATE_ARMED_AWAY:
 | 
			
		||||
      return LOG_STR("ARMED_AWAY");
 | 
			
		||||
    case ACP_STATE_ARMED_NIGHT:
 | 
			
		||||
      return LOG_STR("NIGHT");
 | 
			
		||||
    case ACP_STATE_ARMED_VACATION:
 | 
			
		||||
      return LOG_STR("ARMED_VACATION");
 | 
			
		||||
    case ACP_STATE_ARMED_CUSTOM_BYPASS:
 | 
			
		||||
      return LOG_STR("ARMED_CUSTOM_BYPASS");
 | 
			
		||||
    case ACP_STATE_PENDING:
 | 
			
		||||
      return LOG_STR("PENDING");
 | 
			
		||||
    case ACP_STATE_ARMING:
 | 
			
		||||
      return LOG_STR("ARMING");
 | 
			
		||||
    case ACP_STATE_DISARMING:
 | 
			
		||||
      return LOG_STR("DISARMING");
 | 
			
		||||
    case ACP_STATE_TRIGGERED:
 | 
			
		||||
      return LOG_STR("TRIGGERED");
 | 
			
		||||
    default:
 | 
			
		||||
      return LOG_STR("UNKNOWN");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace alarm_control_panel
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace alarm_control_panel {
 | 
			
		||||
 | 
			
		||||
enum AlarmControlPanelState : uint8_t {
 | 
			
		||||
  ACP_STATE_DISARMED = 0,
 | 
			
		||||
  ACP_STATE_ARMED_HOME = 1,
 | 
			
		||||
  ACP_STATE_ARMED_AWAY = 2,
 | 
			
		||||
  ACP_STATE_ARMED_NIGHT = 3,
 | 
			
		||||
  ACP_STATE_ARMED_VACATION = 4,
 | 
			
		||||
  ACP_STATE_ARMED_CUSTOM_BYPASS = 5,
 | 
			
		||||
  ACP_STATE_PENDING = 6,
 | 
			
		||||
  ACP_STATE_ARMING = 7,
 | 
			
		||||
  ACP_STATE_DISARMING = 8,
 | 
			
		||||
  ACP_STATE_TRIGGERED = 9
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** Returns a string representation of the state.
 | 
			
		||||
 *
 | 
			
		||||
 * @param state The AlarmControlPanelState.
 | 
			
		||||
 */
 | 
			
		||||
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state);
 | 
			
		||||
 | 
			
		||||
}  // namespace alarm_control_panel
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,115 +0,0 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "alarm_control_panel.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace alarm_control_panel {
 | 
			
		||||
 | 
			
		||||
class StateTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit StateTrigger(AlarmControlPanel *alarm_control_panel) {
 | 
			
		||||
    alarm_control_panel->add_on_state_callback([this]() { this->trigger(); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TriggeredTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit TriggeredTrigger(AlarmControlPanel *alarm_control_panel) {
 | 
			
		||||
    alarm_control_panel->add_on_triggered_callback([this]() { this->trigger(); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ClearedTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
 | 
			
		||||
    alarm_control_panel->add_on_cleared_callback([this]() { this->trigger(); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class ArmAwayAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit ArmAwayAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
 | 
			
		||||
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, code)
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    auto call = this->alarm_control_panel_->make_call();
 | 
			
		||||
    auto code = this->code_.optional_value(x...);
 | 
			
		||||
    if (code.has_value()) {
 | 
			
		||||
      call.set_code(code.value());
 | 
			
		||||
    }
 | 
			
		||||
    call.arm_away();
 | 
			
		||||
    call.perform();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  AlarmControlPanel *alarm_control_panel_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class ArmHomeAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit ArmHomeAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
 | 
			
		||||
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, code)
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    auto call = this->alarm_control_panel_->make_call();
 | 
			
		||||
    auto code = this->code_.optional_value(x...);
 | 
			
		||||
    if (code.has_value()) {
 | 
			
		||||
      call.set_code(code.value());
 | 
			
		||||
    }
 | 
			
		||||
    call.arm_home();
 | 
			
		||||
    call.perform();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  AlarmControlPanel *alarm_control_panel_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class DisarmAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
 | 
			
		||||
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, code)
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->alarm_control_panel_->disarm(this->code_.optional_value(x...)); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  AlarmControlPanel *alarm_control_panel_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class PendingAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit PendingAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->alarm_control_panel_->make_call().pending().perform(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  AlarmControlPanel *alarm_control_panel_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class TriggeredAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit TriggeredAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->alarm_control_panel_->make_call().triggered().perform(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  AlarmControlPanel *alarm_control_panel_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class AlarmControlPanelCondition : public Condition<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  AlarmControlPanelCondition(AlarmControlPanel *parent) : parent_(parent) {}
 | 
			
		||||
  bool check(Ts... x) override {
 | 
			
		||||
    return this->parent_->is_state_armed(this->parent_->get_state()) ||
 | 
			
		||||
           this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_TRIGGERED;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  AlarmControlPanel *parent_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace alarm_control_panel
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
CODEOWNERS = ["@jan-hofmeier"]
 | 
			
		||||
@@ -1,189 +0,0 @@
 | 
			
		||||
#include "alpha3.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include <lwip/sockets.h>  //gives ntohl
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace alpha3 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "alpha3";
 | 
			
		||||
 | 
			
		||||
void Alpha3::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ALPHA3");
 | 
			
		||||
  LOG_SENSOR(" ", "Flow", this->flow_sensor_);
 | 
			
		||||
  LOG_SENSOR(" ", "Head", this->head_sensor_);
 | 
			
		||||
  LOG_SENSOR(" ", "Power", this->power_sensor_);
 | 
			
		||||
  LOG_SENSOR(" ", "Current", this->current_sensor_);
 | 
			
		||||
  LOG_SENSOR(" ", "Speed", this->speed_sensor_);
 | 
			
		||||
  LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Alpha3::setup() {}
 | 
			
		||||
 | 
			
		||||
void Alpha3::extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset,
 | 
			
		||||
                                           int16_t value_offset, sensor::Sensor *sensor, float factor) {
 | 
			
		||||
  if (sensor == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
  // we need to handle cases where a value is split over two packets
 | 
			
		||||
  const int16_t value_length = 4;  // 32bit float
 | 
			
		||||
  // offset inside current response packet
 | 
			
		||||
  auto rel_offset = value_offset - response_offset;
 | 
			
		||||
  if (rel_offset <= -value_length)
 | 
			
		||||
    return;  // aready passed the value completly
 | 
			
		||||
  if (rel_offset >= length)
 | 
			
		||||
    return;  // value not in this packet
 | 
			
		||||
 | 
			
		||||
  auto start_offset = std::max(0, rel_offset);
 | 
			
		||||
  auto end_offset = std::min((int16_t) (rel_offset + value_length), length);
 | 
			
		||||
  auto copy_length = end_offset - start_offset;
 | 
			
		||||
  auto buffer_offset = std::max(-rel_offset, 0);
 | 
			
		||||
  std::memcpy(this->buffer_ + buffer_offset, response + start_offset, copy_length);
 | 
			
		||||
 | 
			
		||||
  if (rel_offset + value_length <= length) {
 | 
			
		||||
    // we have the whole value
 | 
			
		||||
    void *buffer = this->buffer_;                          // to prevent warnings when casting the pointer
 | 
			
		||||
    *((int32_t *) buffer) = ntohl(*((int32_t *) buffer));  // values are big endian
 | 
			
		||||
    float fvalue = *((float *) buffer);
 | 
			
		||||
    sensor->publish_state(fvalue * factor);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Alpha3::is_current_response_type_(const uint8_t *response_type) {
 | 
			
		||||
  return !std::memcmp(this->response_type_, response_type, GENI_RESPONSE_TYPE_LENGTH);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) {
 | 
			
		||||
  if (this->response_offset_ >= this->response_length_) {
 | 
			
		||||
    ESP_LOGD(TAG, "[%s] GENI response begin", this->parent_->address_str().c_str());
 | 
			
		||||
    if (length < GENI_RESPONSE_HEADER_LENGTH) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] response to short", this->parent_->address_str().c_str());
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (response[0] != 36 || response[2] != 248 || response[3] != 231 || response[4] != 10) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] response bytes %d %d %d %d %d don't match GENI HEADER", this->parent_->address_str().c_str(),
 | 
			
		||||
               response[0], response[1], response[2], response[3], response[4]);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this->response_length_ = response[1] - GENI_RESPONSE_HEADER_LENGTH + 2;  // maybe 2 byte checksum
 | 
			
		||||
    this->response_offset_ = -GENI_RESPONSE_HEADER_LENGTH;
 | 
			
		||||
    std::memcpy(this->response_type_, response + 5, GENI_RESPONSE_TYPE_LENGTH);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto extract_publish_sensor_value = [response, length, this](int16_t value_offset, sensor::Sensor *sensor,
 | 
			
		||||
                                                               float factor) {
 | 
			
		||||
    this->extract_publish_sensor_value_(response, length, this->response_offset_, value_offset, sensor, factor);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  if (this->is_current_response_type_(GENI_RESPONSE_TYPE_FLOW_HEAD)) {
 | 
			
		||||
    ESP_LOGD(TAG, "[%s] FLOW HEAD Response", this->parent_->address_str().c_str());
 | 
			
		||||
    extract_publish_sensor_value(GENI_RESPONSE_FLOW_OFFSET, this->flow_sensor_, 3600.0F);
 | 
			
		||||
    extract_publish_sensor_value(GENI_RESPONSE_HEAD_OFFSET, this->head_sensor_, .0001F);
 | 
			
		||||
  } else if (this->is_current_response_type_(GENI_RESPONSE_TYPE_POWER)) {
 | 
			
		||||
    ESP_LOGD(TAG, "[%s] POWER Response", this->parent_->address_str().c_str());
 | 
			
		||||
    extract_publish_sensor_value(GENI_RESPONSE_POWER_OFFSET, this->power_sensor_, 1.0F);
 | 
			
		||||
    extract_publish_sensor_value(GENI_RESPONSE_CURRENT_OFFSET, this->current_sensor_, 1.0F);
 | 
			
		||||
    extract_publish_sensor_value(GENI_RESPONSE_MOTOR_SPEED_OFFSET, this->speed_sensor_, 1.0F);
 | 
			
		||||
    extract_publish_sensor_value(GENI_RESPONSE_VOLTAGE_AC_OFFSET, this->voltage_sensor_, 1.0F);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGW(TAG, "unkown GENI response Type %d %d %d %d %d %d %d %d", this->response_type_[0], this->response_type_[1],
 | 
			
		||||
             this->response_type_[2], this->response_type_[3], this->response_type_[4], this->response_type_[5],
 | 
			
		||||
             this->response_type_[6], this->response_type_[7]);
 | 
			
		||||
  }
 | 
			
		||||
  this->response_offset_ += length;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      this->response_offset_ = 0;
 | 
			
		||||
      this->response_length_ = 0;
 | 
			
		||||
      ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str());
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_CONNECT_EVT: {
 | 
			
		||||
      if (std::memcmp(param->connect.remote_bda, this->parent_->get_remote_bda(), 6) != 0)
 | 
			
		||||
        return;
 | 
			
		||||
      auto ret = esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT);
 | 
			
		||||
      if (ret) {
 | 
			
		||||
        ESP_LOGW(TAG, "esp_ble_set_encryption failed, status=%x", ret);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
      this->node_state = espbt::ClientState::IDLE;
 | 
			
		||||
      if (this->flow_sensor_ != nullptr)
 | 
			
		||||
        this->flow_sensor_->publish_state(NAN);
 | 
			
		||||
      if (this->head_sensor_ != nullptr)
 | 
			
		||||
        this->head_sensor_->publish_state(NAN);
 | 
			
		||||
      if (this->power_sensor_ != nullptr)
 | 
			
		||||
        this->power_sensor_->publish_state(NAN);
 | 
			
		||||
      if (this->current_sensor_ != nullptr)
 | 
			
		||||
        this->current_sensor_->publish_state(NAN);
 | 
			
		||||
      if (this->speed_sensor_ != nullptr)
 | 
			
		||||
        this->speed_sensor_->publish_state(NAN);
 | 
			
		||||
      if (this->speed_sensor_ != nullptr)
 | 
			
		||||
        this->voltage_sensor_->publish_state(NAN);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
      auto *chr = this->parent_->get_characteristic(ALPHA3_GENI_SERVICE_UUID, ALPHA3_GENI_CHARACTERISTIC_UUID);
 | 
			
		||||
      if (chr == nullptr) {
 | 
			
		||||
        ESP_LOGE(TAG, "[%s] No GENI service found at device, not an Alpha3..?", this->parent_->address_str().c_str());
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
 | 
			
		||||
                                                      chr->handle);
 | 
			
		||||
      if (status) {
 | 
			
		||||
        ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
 | 
			
		||||
      }
 | 
			
		||||
      this->geni_handle_ = chr->handle;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      this->node_state = espbt::ClientState::ESTABLISHED;
 | 
			
		||||
      this->update();
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_NOTIFY_EVT: {
 | 
			
		||||
      if (param->notify.handle == this->geni_handle_) {
 | 
			
		||||
        this->handle_geni_response_(param->notify.value, param->notify.value_len);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Alpha3::send_request_(uint8_t *request, size_t len) {
 | 
			
		||||
  auto status =
 | 
			
		||||
      esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->geni_handle_, len,
 | 
			
		||||
                               request, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status)
 | 
			
		||||
    ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Alpha3::update() {
 | 
			
		||||
  if (this->node_state != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->flow_sensor_ != nullptr || this->head_sensor_ != nullptr) {
 | 
			
		||||
    uint8_t geni_request_flow_head[] = {39, 7, 231, 248, 10, 3, 93, 1, 33, 82, 31};
 | 
			
		||||
    this->send_request_(geni_request_flow_head, sizeof(geni_request_flow_head));
 | 
			
		||||
    delay(25);  // need to wait between requests
 | 
			
		||||
  }
 | 
			
		||||
  if (this->power_sensor_ != nullptr || this->current_sensor_ != nullptr || this->speed_sensor_ != nullptr ||
 | 
			
		||||
      this->voltage_sensor_ != nullptr) {
 | 
			
		||||
    uint8_t geni_request_power[] = {39, 7, 231, 248, 10, 3, 87, 0, 69, 138, 205};
 | 
			
		||||
    this->send_request_(geni_request_power, sizeof(geni_request_power));
 | 
			
		||||
    delay(25);  // need to wait between requests
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}  // namespace alpha3
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -1,73 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/ble_client/ble_client.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace alpha3 {
 | 
			
		||||
 | 
			
		||||
namespace espbt = esphome::esp32_ble_tracker;
 | 
			
		||||
 | 
			
		||||
static const espbt::ESPBTUUID ALPHA3_GENI_SERVICE_UUID = espbt::ESPBTUUID::from_uint16(0xfe5d);
 | 
			
		||||
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID =
 | 
			
		||||
    espbt::ESPBTUUID::from_raw({static_cast<char>(0xa9), 0x7b, static_cast<char>(0xb8), static_cast<char>(0x85), 0x0,
 | 
			
		||||
                                0x1a, 0x28, static_cast<char>(0xaa), 0x2a, 0x43, 0x6e, 0x3, static_cast<char>(0xd1),
 | 
			
		||||
                                static_cast<char>(0xff), static_cast<char>(0x9c), static_cast<char>(0x85)});
 | 
			
		||||
static const int16_t GENI_RESPONSE_HEADER_LENGTH = 13;
 | 
			
		||||
static const size_t GENI_RESPONSE_TYPE_LENGTH = 8;
 | 
			
		||||
 | 
			
		||||
static const uint8_t GENI_RESPONSE_TYPE_FLOW_HEAD[GENI_RESPONSE_TYPE_LENGTH] = {31, 0, 1, 48, 1, 0, 0, 24};
 | 
			
		||||
static const int16_t GENI_RESPONSE_FLOW_OFFSET = 0;
 | 
			
		||||
static const int16_t GENI_RESPONSE_HEAD_OFFSET = 4;
 | 
			
		||||
 | 
			
		||||
static const uint8_t GENI_RESPONSE_TYPE_POWER[GENI_RESPONSE_TYPE_LENGTH] = {44, 0, 1, 0, 1, 0, 0, 37};
 | 
			
		||||
static const int16_t GENI_RESPONSE_VOLTAGE_AC_OFFSET = 0;
 | 
			
		||||
static const int16_t GENI_RESPONSE_VOLTAGE_DC_OFFSET = 4;
 | 
			
		||||
static const int16_t GENI_RESPONSE_CURRENT_OFFSET = 8;
 | 
			
		||||
static const int16_t GENI_RESPONSE_POWER_OFFSET = 12;
 | 
			
		||||
static const int16_t GENI_RESPONSE_MOTOR_POWER_OFFSET = 16;  // not sure
 | 
			
		||||
static const int16_t GENI_RESPONSE_MOTOR_SPEED_OFFSET = 20;
 | 
			
		||||
 | 
			
		||||
class Alpha3 : public esphome::ble_client::BLEClientNode, public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
			
		||||
  void set_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; }
 | 
			
		||||
  void set_head_sensor(sensor::Sensor *sensor) { this->head_sensor_ = sensor; }
 | 
			
		||||
  void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; }
 | 
			
		||||
  void set_current_sensor(sensor::Sensor *sensor) { this->current_sensor_ = sensor; }
 | 
			
		||||
  void set_speed_sensor(sensor::Sensor *sensor) { this->speed_sensor_ = sensor; }
 | 
			
		||||
  void set_voltage_sensor(sensor::Sensor *sensor) { this->voltage_sensor_ = sensor; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  sensor::Sensor *flow_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *head_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *power_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *current_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *speed_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *voltage_sensor_{nullptr};
 | 
			
		||||
  uint16_t geni_handle_;
 | 
			
		||||
  int16_t response_length_;
 | 
			
		||||
  int16_t response_offset_;
 | 
			
		||||
  uint8_t response_type_[GENI_RESPONSE_TYPE_LENGTH];
 | 
			
		||||
  uint8_t buffer_[4];
 | 
			
		||||
  void extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset,
 | 
			
		||||
                                     int16_t value_offset, sensor::Sensor *sensor, float factor);
 | 
			
		||||
  void handle_geni_response_(const uint8_t *response, uint16_t length);
 | 
			
		||||
  void send_request_(uint8_t *request, size_t len);
 | 
			
		||||
  bool is_current_response_type_(const uint8_t *response_type);
 | 
			
		||||
};
 | 
			
		||||
}  // namespace alpha3
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -1,85 +0,0 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, ble_client
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_CURRENT,
 | 
			
		||||
    CONF_FLOW,
 | 
			
		||||
    CONF_HEAD,
 | 
			
		||||
    CONF_POWER,
 | 
			
		||||
    CONF_SPEED,
 | 
			
		||||
    CONF_VOLTAGE,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
    UNIT_METER,
 | 
			
		||||
    UNIT_CUBIC_METER_PER_HOUR,
 | 
			
		||||
    UNIT_REVOLUTIONS_PER_MINUTE,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
alpha3_ns = cg.esphome_ns.namespace("alpha3")
 | 
			
		||||
Alpha3 = alpha3_ns.class_("Alpha3", ble_client.BLEClientNode, cg.PollingComponent)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Alpha3),
 | 
			
		||||
            cv.Optional(CONF_FLOW): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_CUBIC_METER_PER_HOUR,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_HEAD): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_METER,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_POWER): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_WATT,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CURRENT): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_AMPERE,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_SPEED): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_VOLT,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(ble_client.BLE_CLIENT_SCHEMA)
 | 
			
		||||
    .extend(cv.polling_component_schema("15s"))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
 | 
			
		||||
    if CONF_FLOW in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_FLOW])
 | 
			
		||||
        cg.add(var.set_flow_sensor(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_HEAD in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_HEAD])
 | 
			
		||||
        cg.add(var.set_head_sensor(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_POWER in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_POWER])
 | 
			
		||||
        cg.add(var.set_power_sensor(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_CURRENT in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_CURRENT])
 | 
			
		||||
        cg.add(var.set_current_sensor(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_SPEED in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_SPEED])
 | 
			
		||||
        cg.add(var.set_speed_sensor(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_VOLTAGE in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_VOLTAGE])
 | 
			
		||||
        cg.add(var.set_voltage_sensor(sens))
 | 
			
		||||
@@ -1,124 +1,35 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from esphome import automation, core
 | 
			
		||||
from esphome.components import font
 | 
			
		||||
from esphome import core
 | 
			
		||||
from esphome.components import display, font
 | 
			
		||||
import esphome.components.image as espImage
 | 
			
		||||
from esphome.components.image import CONF_USE_TRANSPARENCY
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_FILE,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_RAW_DATA_ID,
 | 
			
		||||
    CONF_REPEAT,
 | 
			
		||||
    CONF_RESIZE,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
)
 | 
			
		||||
from esphome.const import CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_RESIZE, CONF_TYPE
 | 
			
		||||
from esphome.core import CORE, HexInt
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["image"]
 | 
			
		||||
CODEOWNERS = ["@syndlex"]
 | 
			
		||||
DEPENDENCIES = ["display"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONF_LOOP = "loop"
 | 
			
		||||
CONF_START_FRAME = "start_frame"
 | 
			
		||||
CONF_END_FRAME = "end_frame"
 | 
			
		||||
CONF_FRAME = "frame"
 | 
			
		||||
 | 
			
		||||
animation_ns = cg.esphome_ns.namespace("animation")
 | 
			
		||||
 | 
			
		||||
Animation_ = animation_ns.class_("Animation", espImage.Image_)
 | 
			
		||||
 | 
			
		||||
# Actions
 | 
			
		||||
NextFrameAction = animation_ns.class_(
 | 
			
		||||
    "AnimationNextFrameAction", automation.Action, cg.Parented.template(Animation_)
 | 
			
		||||
)
 | 
			
		||||
PrevFrameAction = animation_ns.class_(
 | 
			
		||||
    "AnimationPrevFrameAction", automation.Action, cg.Parented.template(Animation_)
 | 
			
		||||
)
 | 
			
		||||
SetFrameAction = animation_ns.class_(
 | 
			
		||||
    "AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_cross_dependencies(config):
 | 
			
		||||
    """
 | 
			
		||||
    Validate fields whose possible values depend on other fields.
 | 
			
		||||
    For example, validate that explicitly transparent image types
 | 
			
		||||
    have "use_transparency" set to True.
 | 
			
		||||
    Also set the default value for those kind of dependent fields.
 | 
			
		||||
    """
 | 
			
		||||
    image_type = config[CONF_TYPE]
 | 
			
		||||
    is_transparent_type = image_type in ["TRANSPARENT_BINARY", "RGBA"]
 | 
			
		||||
    # If the use_transparency option was not specified, set the default depending on the image type
 | 
			
		||||
    if CONF_USE_TRANSPARENCY not in config:
 | 
			
		||||
        config[CONF_USE_TRANSPARENCY] = is_transparent_type
 | 
			
		||||
 | 
			
		||||
    if is_transparent_type and not config[CONF_USE_TRANSPARENCY]:
 | 
			
		||||
        raise cv.Invalid(f"Image type {image_type} must always be transparent.")
 | 
			
		||||
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
Animation_ = display.display_ns.class_("Animation", espImage.Image_)
 | 
			
		||||
 | 
			
		||||
ANIMATION_SCHEMA = cv.Schema(
 | 
			
		||||
    cv.All(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_ID): cv.declare_id(Animation_),
 | 
			
		||||
            cv.Required(CONF_FILE): cv.file_,
 | 
			
		||||
            cv.Optional(CONF_RESIZE): cv.dimensions,
 | 
			
		||||
            cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
 | 
			
		||||
                espImage.IMAGE_TYPE, upper=True
 | 
			
		||||
            ),
 | 
			
		||||
            # Not setting default here on purpose; the default depends on the image type,
 | 
			
		||||
            # and thus will be set in the "validate_cross_dependencies" validator.
 | 
			
		||||
            cv.Optional(CONF_USE_TRANSPARENCY): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_LOOP): cv.All(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Optional(CONF_START_FRAME, default=0): cv.positive_int,
 | 
			
		||||
                    cv.Optional(CONF_END_FRAME): cv.positive_int,
 | 
			
		||||
                    cv.Optional(CONF_REPEAT): cv.positive_int,
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
 | 
			
		||||
        },
 | 
			
		||||
        validate_cross_dependencies,
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.declare_id(Animation_),
 | 
			
		||||
        cv.Required(CONF_FILE): cv.file_,
 | 
			
		||||
        cv.Optional(CONF_RESIZE): cv.dimensions,
 | 
			
		||||
        cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
 | 
			
		||||
            espImage.IMAGE_TYPE, upper=True
 | 
			
		||||
        ),
 | 
			
		||||
        cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
 | 
			
		||||
 | 
			
		||||
NEXT_FRAME_SCHEMA = automation.maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(Animation_),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
PREV_FRAME_SCHEMA = automation.maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(Animation_),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
SET_FRAME_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(Animation_),
 | 
			
		||||
        cv.Required(CONF_FRAME): cv.uint16_t,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("animation.next_frame", NextFrameAction, NEXT_FRAME_SCHEMA)
 | 
			
		||||
@automation.register_action("animation.prev_frame", PrevFrameAction, PREV_FRAME_SCHEMA)
 | 
			
		||||
@automation.register_action("animation.set_frame", SetFrameAction, SET_FRAME_SCHEMA)
 | 
			
		||||
async def animation_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
    if CONF_FRAME in config:
 | 
			
		||||
        template_ = await cg.templatable(config[CONF_FRAME], args, cg.uint16)
 | 
			
		||||
        cg.add(var.set_frame(template_))
 | 
			
		||||
    return var
 | 
			
		||||
CODEOWNERS = ["@syndlex"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
@@ -139,19 +50,16 @@ async def to_code(config):
 | 
			
		||||
    else:
 | 
			
		||||
        if width > 500 or height > 500:
 | 
			
		||||
            _LOGGER.warning(
 | 
			
		||||
                'The image "%s" you requested is very big. Please consider'
 | 
			
		||||
                " using the resize parameter.",
 | 
			
		||||
                path,
 | 
			
		||||
                "The image you requested is very big. Please consider using"
 | 
			
		||||
                " the resize parameter."
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    transparent = config[CONF_USE_TRANSPARENCY]
 | 
			
		||||
 | 
			
		||||
    if config[CONF_TYPE] == "GRAYSCALE":
 | 
			
		||||
        data = [0 for _ in range(height * width * frames)]
 | 
			
		||||
        pos = 0
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            frame = image.convert("LA", dither=Image.NONE)
 | 
			
		||||
            frame = image.convert("L", dither=Image.NONE)
 | 
			
		||||
            if CONF_RESIZE in config:
 | 
			
		||||
                frame = frame.resize([width, height])
 | 
			
		||||
            pixels = list(frame.getdata())
 | 
			
		||||
@@ -159,22 +67,16 @@ async def to_code(config):
 | 
			
		||||
                raise core.EsphomeError(
 | 
			
		||||
                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
 | 
			
		||||
                )
 | 
			
		||||
            for pix, a in pixels:
 | 
			
		||||
                if transparent:
 | 
			
		||||
                    if pix == 1:
 | 
			
		||||
                        pix = 0
 | 
			
		||||
                    if a < 0x80:
 | 
			
		||||
                        pix = 1
 | 
			
		||||
 | 
			
		||||
            for pix in pixels:
 | 
			
		||||
                data[pos] = pix
 | 
			
		||||
                pos += 1
 | 
			
		||||
 | 
			
		||||
    elif config[CONF_TYPE] == "RGBA":
 | 
			
		||||
        data = [0 for _ in range(height * width * 4 * frames)]
 | 
			
		||||
    elif config[CONF_TYPE] == "RGB24":
 | 
			
		||||
        data = [0 for _ in range(height * width * 3 * frames)]
 | 
			
		||||
        pos = 0
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            frame = image.convert("RGBA")
 | 
			
		||||
            frame = image.convert("RGB")
 | 
			
		||||
            if CONF_RESIZE in config:
 | 
			
		||||
                frame = frame.resize([width, height])
 | 
			
		||||
            pixels = list(frame.getdata())
 | 
			
		||||
@@ -189,44 +91,13 @@ async def to_code(config):
 | 
			
		||||
                pos += 1
 | 
			
		||||
                data[pos] = pix[2]
 | 
			
		||||
                pos += 1
 | 
			
		||||
                data[pos] = pix[3]
 | 
			
		||||
                pos += 1
 | 
			
		||||
 | 
			
		||||
    elif config[CONF_TYPE] == "RGB24":
 | 
			
		||||
        data = [0 for _ in range(height * width * 3 * frames)]
 | 
			
		||||
        pos = 0
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            frame = image.convert("RGBA")
 | 
			
		||||
            if CONF_RESIZE in config:
 | 
			
		||||
                frame = frame.resize([width, height])
 | 
			
		||||
            pixels = list(frame.getdata())
 | 
			
		||||
            if len(pixels) != height * width:
 | 
			
		||||
                raise core.EsphomeError(
 | 
			
		||||
                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
 | 
			
		||||
                )
 | 
			
		||||
            for r, g, b, a in pixels:
 | 
			
		||||
                if transparent:
 | 
			
		||||
                    if r == 0 and g == 0 and b == 1:
 | 
			
		||||
                        b = 0
 | 
			
		||||
                    if a < 0x80:
 | 
			
		||||
                        r = 0
 | 
			
		||||
                        g = 0
 | 
			
		||||
                        b = 1
 | 
			
		||||
 | 
			
		||||
                data[pos] = r
 | 
			
		||||
                pos += 1
 | 
			
		||||
                data[pos] = g
 | 
			
		||||
                pos += 1
 | 
			
		||||
                data[pos] = b
 | 
			
		||||
                pos += 1
 | 
			
		||||
 | 
			
		||||
    elif config[CONF_TYPE] in ["RGB565", "TRANSPARENT_IMAGE"]:
 | 
			
		||||
    elif config[CONF_TYPE] == "RGB565":
 | 
			
		||||
        data = [0 for _ in range(height * width * 2 * frames)]
 | 
			
		||||
        pos = 0
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            frame = image.convert("RGBA")
 | 
			
		||||
            frame = image.convert("RGB")
 | 
			
		||||
            if CONF_RESIZE in config:
 | 
			
		||||
                frame = frame.resize([width, height])
 | 
			
		||||
            pixels = list(frame.getdata())
 | 
			
		||||
@@ -234,21 +105,14 @@ async def to_code(config):
 | 
			
		||||
                raise core.EsphomeError(
 | 
			
		||||
                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
 | 
			
		||||
                )
 | 
			
		||||
            for r, g, b, a in pixels:
 | 
			
		||||
                R = r >> 3
 | 
			
		||||
                G = g >> 2
 | 
			
		||||
                B = b >> 3
 | 
			
		||||
            for pix in pixels:
 | 
			
		||||
                R = pix[0] >> 3
 | 
			
		||||
                G = pix[1] >> 2
 | 
			
		||||
                B = pix[2] >> 3
 | 
			
		||||
                rgb = (R << 11) | (G << 5) | B
 | 
			
		||||
 | 
			
		||||
                if transparent:
 | 
			
		||||
                    if rgb == 0x0020:
 | 
			
		||||
                        rgb = 0
 | 
			
		||||
                    if a < 0x80:
 | 
			
		||||
                        rgb = 0x0020
 | 
			
		||||
 | 
			
		||||
                data[pos] = rgb >> 8
 | 
			
		||||
                pos += 1
 | 
			
		||||
                data[pos] = rgb & 0xFF
 | 
			
		||||
                data[pos] = rgb & 255
 | 
			
		||||
                pos += 1
 | 
			
		||||
 | 
			
		||||
    elif config[CONF_TYPE] in ["BINARY", "TRANSPARENT_BINARY"]:
 | 
			
		||||
@@ -256,31 +120,19 @@ async def to_code(config):
 | 
			
		||||
        data = [0 for _ in range((height * width8 // 8) * frames)]
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            if transparent:
 | 
			
		||||
                alpha = image.split()[-1]
 | 
			
		||||
                has_alpha = alpha.getextrema()[0] < 0xFF
 | 
			
		||||
            frame = image.convert("1", dither=Image.NONE)
 | 
			
		||||
            if CONF_RESIZE in config:
 | 
			
		||||
                frame = frame.resize([width, height])
 | 
			
		||||
                if transparent:
 | 
			
		||||
                    alpha = alpha.resize([width, height])
 | 
			
		||||
            for x, y in [(i, j) for i in range(width) for j in range(height)]:
 | 
			
		||||
                if transparent and has_alpha:
 | 
			
		||||
                    if not alpha.getpixel((x, y)):
 | 
			
		||||
            for y in range(height):
 | 
			
		||||
                for x in range(width):
 | 
			
		||||
                    if frame.getpixel((x, y)):
 | 
			
		||||
                        continue
 | 
			
		||||
                elif frame.getpixel((x, y)):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                pos = x + y * width8 + (height * width8 * frameIndex)
 | 
			
		||||
                data[pos // 8] |= 0x80 >> (pos % 8)
 | 
			
		||||
    else:
 | 
			
		||||
        raise core.EsphomeError(
 | 
			
		||||
            f"Animation f{config[CONF_ID]} has not supported type {config[CONF_TYPE]}."
 | 
			
		||||
        )
 | 
			
		||||
                    pos = x + y * width8 + (height * width8 * frameIndex)
 | 
			
		||||
                    data[pos // 8] |= 0x80 >> (pos % 8)
 | 
			
		||||
 | 
			
		||||
    rhs = [HexInt(x) for x in data]
 | 
			
		||||
    prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
 | 
			
		||||
    var = cg.new_Pvariable(
 | 
			
		||||
    cg.new_Pvariable(
 | 
			
		||||
        config[CONF_ID],
 | 
			
		||||
        prog_arr,
 | 
			
		||||
        width,
 | 
			
		||||
@@ -288,9 +140,3 @@ async def to_code(config):
 | 
			
		||||
        frames,
 | 
			
		||||
        espImage.IMAGE_TYPE[config[CONF_TYPE]],
 | 
			
		||||
    )
 | 
			
		||||
    cg.add(var.set_transparency(transparent))
 | 
			
		||||
    if CONF_LOOP in config:
 | 
			
		||||
        start = config[CONF_LOOP][CONF_START_FRAME]
 | 
			
		||||
        end = config[CONF_LOOP].get(CONF_END_FRAME, frames)
 | 
			
		||||
        count = config[CONF_LOOP].get(CONF_REPEAT, -1)
 | 
			
		||||
        cg.add(var.set_loop(start, end, count))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,70 +0,0 @@
 | 
			
		||||
#include "animation.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace animation {
 | 
			
		||||
 | 
			
		||||
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count,
 | 
			
		||||
                     image::ImageType type)
 | 
			
		||||
    : Image(data_start, width, height, type),
 | 
			
		||||
      animation_data_start_(data_start),
 | 
			
		||||
      current_frame_(0),
 | 
			
		||||
      animation_frame_count_(animation_frame_count),
 | 
			
		||||
      loop_start_frame_(0),
 | 
			
		||||
      loop_end_frame_(animation_frame_count_),
 | 
			
		||||
      loop_count_(0),
 | 
			
		||||
      loop_current_iteration_(1) {}
 | 
			
		||||
void Animation::set_loop(uint32_t start_frame, uint32_t end_frame, int count) {
 | 
			
		||||
  loop_start_frame_ = std::min(start_frame, animation_frame_count_);
 | 
			
		||||
  loop_end_frame_ = std::min(end_frame, animation_frame_count_);
 | 
			
		||||
  loop_count_ = count;
 | 
			
		||||
  loop_current_iteration_ = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t Animation::get_animation_frame_count() const { return this->animation_frame_count_; }
 | 
			
		||||
int Animation::get_current_frame() const { return this->current_frame_; }
 | 
			
		||||
void Animation::next_frame() {
 | 
			
		||||
  this->current_frame_++;
 | 
			
		||||
  if (loop_count_ && this->current_frame_ == loop_end_frame_ &&
 | 
			
		||||
      (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) {
 | 
			
		||||
    this->current_frame_ = loop_start_frame_;
 | 
			
		||||
    this->loop_current_iteration_++;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->current_frame_ >= animation_frame_count_) {
 | 
			
		||||
    this->loop_current_iteration_ = 1;
 | 
			
		||||
    this->current_frame_ = 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->update_data_start_();
 | 
			
		||||
}
 | 
			
		||||
void Animation::prev_frame() {
 | 
			
		||||
  this->current_frame_--;
 | 
			
		||||
  if (this->current_frame_ < 0) {
 | 
			
		||||
    this->current_frame_ = this->animation_frame_count_ - 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->update_data_start_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Animation::set_frame(int frame) {
 | 
			
		||||
  unsigned abs_frame = abs(frame);
 | 
			
		||||
 | 
			
		||||
  if (abs_frame < this->animation_frame_count_) {
 | 
			
		||||
    if (frame >= 0) {
 | 
			
		||||
      this->current_frame_ = frame;
 | 
			
		||||
    } else {
 | 
			
		||||
      this->current_frame_ = this->animation_frame_count_ - abs_frame;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->update_data_start_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Animation::update_data_start_() {
 | 
			
		||||
  const uint32_t image_size = image_type_to_width_stride(this->width_, this->type_) * this->height_;
 | 
			
		||||
  this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace animation
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,67 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "esphome/components/image/image.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace animation {
 | 
			
		||||
 | 
			
		||||
class Animation : public image::Image {
 | 
			
		||||
 public:
 | 
			
		||||
  Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, image::ImageType type);
 | 
			
		||||
 | 
			
		||||
  uint32_t get_animation_frame_count() const;
 | 
			
		||||
  int get_current_frame() const;
 | 
			
		||||
  void next_frame();
 | 
			
		||||
  void prev_frame();
 | 
			
		||||
 | 
			
		||||
  /** Selects a specific frame within the animation.
 | 
			
		||||
   *
 | 
			
		||||
   * @param frame If possitive, advance to the frame. If negative, recede to that frame from the end frame.
 | 
			
		||||
   */
 | 
			
		||||
  void set_frame(int frame);
 | 
			
		||||
 | 
			
		||||
  void set_loop(uint32_t start_frame, uint32_t end_frame, int count);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void update_data_start_();
 | 
			
		||||
 | 
			
		||||
  const uint8_t *animation_data_start_;
 | 
			
		||||
  int current_frame_;
 | 
			
		||||
  uint32_t animation_frame_count_;
 | 
			
		||||
  uint32_t loop_start_frame_;
 | 
			
		||||
  uint32_t loop_end_frame_;
 | 
			
		||||
  int loop_count_;
 | 
			
		||||
  int loop_current_iteration_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class AnimationNextFrameAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  AnimationNextFrameAction(Animation *parent) : parent_(parent) {}
 | 
			
		||||
  void play(Ts... x) override { this->parent_->next_frame(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Animation *parent_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class AnimationPrevFrameAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  AnimationPrevFrameAction(Animation *parent) : parent_(parent) {}
 | 
			
		||||
  void play(Ts... x) override { this->parent_->prev_frame(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Animation *parent_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class AnimationSetFrameAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  AnimationSetFrameAction(Animation *parent) : parent_(parent) {}
 | 
			
		||||
  TEMPLATABLE_VALUE(uint16_t, frame)
 | 
			
		||||
  void play(Ts... x) override { this->parent_->set_frame(this->frame_.value(x...)); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Animation *parent_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace animation
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -4,6 +4,7 @@ from esphome.components import i2c
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
AUTO_LOAD = ["sensor", "binary_sensor"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONF_APDS9960_ID = "apds9960_id"
 | 
			
		||||
 
 | 
			
		||||
@@ -116,12 +116,8 @@ void APDS9960::setup() {
 | 
			
		||||
  APDS9960_WRITE_BYTE(0x80, val);
 | 
			
		||||
}
 | 
			
		||||
bool APDS9960::is_color_enabled_() const {
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  return this->red_sensor_ != nullptr || this->green_sensor_ != nullptr || this->blue_sensor_ != nullptr ||
 | 
			
		||||
         this->clear_sensor_ != nullptr;
 | 
			
		||||
#else
 | 
			
		||||
  return false;
 | 
			
		||||
#endif
 | 
			
		||||
  return this->red_channel_ != nullptr || this->green_channel_ != nullptr || this->blue_channel_ != nullptr ||
 | 
			
		||||
         this->clear_channel_ != nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APDS9960::dump_config() {
 | 
			
		||||
@@ -129,15 +125,6 @@ void APDS9960::dump_config() {
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  LOG_SENSOR("  ", "Red channel", this->red_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Green channel", this->green_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Blue channel", this->blue_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Clear channel", this->clear_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Proximity", this->proximity_sensor_);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    switch (this->error_code_) {
 | 
			
		||||
      case COMMUNICATION_FAILED:
 | 
			
		||||
@@ -194,22 +181,17 @@ void APDS9960::read_color_data_(uint8_t status) {
 | 
			
		||||
  float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc);
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  if (this->clear_sensor_ != nullptr)
 | 
			
		||||
    this->clear_sensor_->publish_state(clear_perc);
 | 
			
		||||
  if (this->red_sensor_ != nullptr)
 | 
			
		||||
    this->red_sensor_->publish_state(red_perc);
 | 
			
		||||
  if (this->green_sensor_ != nullptr)
 | 
			
		||||
    this->green_sensor_->publish_state(green_perc);
 | 
			
		||||
  if (this->blue_sensor_ != nullptr)
 | 
			
		||||
    this->blue_sensor_->publish_state(blue_perc);
 | 
			
		||||
#endif
 | 
			
		||||
  if (this->clear_channel_ != nullptr)
 | 
			
		||||
    this->clear_channel_->publish_state(clear_perc);
 | 
			
		||||
  if (this->red_channel_ != nullptr)
 | 
			
		||||
    this->red_channel_->publish_state(red_perc);
 | 
			
		||||
  if (this->green_channel_ != nullptr)
 | 
			
		||||
    this->green_channel_->publish_state(green_perc);
 | 
			
		||||
  if (this->blue_channel_ != nullptr)
 | 
			
		||||
    this->blue_channel_->publish_state(blue_perc);
 | 
			
		||||
}
 | 
			
		||||
void APDS9960::read_proximity_data_(uint8_t status) {
 | 
			
		||||
#ifndef USE_SENSOR
 | 
			
		||||
  return;
 | 
			
		||||
#else
 | 
			
		||||
  if (this->proximity_sensor_ == nullptr)
 | 
			
		||||
  if (this->proximity_ == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if ((status & 0b10) == 0x00) {
 | 
			
		||||
@@ -222,8 +204,7 @@ void APDS9960::read_proximity_data_(uint8_t status) {
 | 
			
		||||
 | 
			
		||||
  float prox_perc = (prox / float(UINT8_MAX)) * 100.0f;
 | 
			
		||||
  ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc);
 | 
			
		||||
  this->proximity_sensor_->publish_state(prox_perc);
 | 
			
		||||
#endif
 | 
			
		||||
  this->proximity_->publish_state(prox_perc);
 | 
			
		||||
}
 | 
			
		||||
void APDS9960::read_gesture_data_() {
 | 
			
		||||
  if (!this->is_gesture_enabled_())
 | 
			
		||||
@@ -275,29 +256,28 @@ void APDS9960::read_gesture_data_() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APDS9960::report_gesture_(int gesture) {
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  binary_sensor::BinarySensor *bin;
 | 
			
		||||
  switch (gesture) {
 | 
			
		||||
    case 1:
 | 
			
		||||
      bin = this->up_direction_binary_sensor_;
 | 
			
		||||
      bin = this->up_direction_;
 | 
			
		||||
      this->gesture_up_started_ = false;
 | 
			
		||||
      this->gesture_down_started_ = false;
 | 
			
		||||
      ESP_LOGD(TAG, "Got gesture UP");
 | 
			
		||||
      break;
 | 
			
		||||
    case 2:
 | 
			
		||||
      bin = this->down_direction_binary_sensor_;
 | 
			
		||||
      bin = this->down_direction_;
 | 
			
		||||
      this->gesture_up_started_ = false;
 | 
			
		||||
      this->gesture_down_started_ = false;
 | 
			
		||||
      ESP_LOGD(TAG, "Got gesture DOWN");
 | 
			
		||||
      break;
 | 
			
		||||
    case 3:
 | 
			
		||||
      bin = this->left_direction_binary_sensor_;
 | 
			
		||||
      bin = this->left_direction_;
 | 
			
		||||
      this->gesture_left_started_ = false;
 | 
			
		||||
      this->gesture_right_started_ = false;
 | 
			
		||||
      ESP_LOGD(TAG, "Got gesture LEFT");
 | 
			
		||||
      break;
 | 
			
		||||
    case 4:
 | 
			
		||||
      bin = this->right_direction_binary_sensor_;
 | 
			
		||||
      bin = this->right_direction_;
 | 
			
		||||
      this->gesture_left_started_ = false;
 | 
			
		||||
      this->gesture_right_started_ = false;
 | 
			
		||||
      ESP_LOGD(TAG, "Got gesture RIGHT");
 | 
			
		||||
@@ -310,7 +290,6 @@ void APDS9960::report_gesture_(int gesture) {
 | 
			
		||||
    bin->publish_state(true);
 | 
			
		||||
    bin->publish_state(false);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
void APDS9960::process_dataset_(int up, int down, int left, int right) {
 | 
			
		||||
  /* Algorithm: (see Figure 11 in datasheet)
 | 
			
		||||
@@ -386,22 +365,10 @@ void APDS9960::process_dataset_(int up, int down, int left, int right) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
float APDS9960::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
bool APDS9960::is_proximity_enabled_() const {
 | 
			
		||||
  return
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
      this->proximity_sensor_ != nullptr
 | 
			
		||||
#else
 | 
			
		||||
      false
 | 
			
		||||
#endif
 | 
			
		||||
      || this->is_gesture_enabled_();
 | 
			
		||||
}
 | 
			
		||||
bool APDS9960::is_proximity_enabled_() const { return this->proximity_ != nullptr || this->is_gesture_enabled_(); }
 | 
			
		||||
bool APDS9960::is_gesture_enabled_() const {
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  return this->up_direction_binary_sensor_ != nullptr || this->left_direction_binary_sensor_ != nullptr ||
 | 
			
		||||
         this->down_direction_binary_sensor_ != nullptr || this->right_direction_binary_sensor_ != nullptr;
 | 
			
		||||
#else
 | 
			
		||||
  return false;
 | 
			
		||||
#endif
 | 
			
		||||
  return this->up_direction_ != nullptr || this->left_direction_ != nullptr || this->down_direction_ != nullptr ||
 | 
			
		||||
         this->right_direction_ != nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace apds9960
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +1,14 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace apds9960 {
 | 
			
		||||
 | 
			
		||||
class APDS9960 : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  SUB_SENSOR(red)
 | 
			
		||||
  SUB_SENSOR(green)
 | 
			
		||||
  SUB_SENSOR(blue)
 | 
			
		||||
  SUB_SENSOR(clear)
 | 
			
		||||
  SUB_SENSOR(proximity)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  SUB_BINARY_SENSOR(up_direction)
 | 
			
		||||
  SUB_BINARY_SENSOR(right_direction)
 | 
			
		||||
  SUB_BINARY_SENSOR(down_direction)
 | 
			
		||||
  SUB_BINARY_SENSOR(left_direction)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
@@ -43,6 +23,16 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
  void set_gesture_gain(uint8_t gain) { this->gesture_gain_ = gain; }
 | 
			
		||||
  void set_gesture_wait_time(uint8_t wait_time) { this->gesture_wait_time_ = wait_time; }
 | 
			
		||||
 | 
			
		||||
  void set_red_channel(sensor::Sensor *red_channel) { red_channel_ = red_channel; }
 | 
			
		||||
  void set_green_channel(sensor::Sensor *green_channel) { green_channel_ = green_channel; }
 | 
			
		||||
  void set_blue_channel(sensor::Sensor *blue_channel) { blue_channel_ = blue_channel; }
 | 
			
		||||
  void set_clear_channel(sensor::Sensor *clear_channel) { clear_channel_ = clear_channel; }
 | 
			
		||||
  void set_up_direction(binary_sensor::BinarySensor *up_direction) { up_direction_ = up_direction; }
 | 
			
		||||
  void set_right_direction(binary_sensor::BinarySensor *right_direction) { right_direction_ = right_direction; }
 | 
			
		||||
  void set_down_direction(binary_sensor::BinarySensor *down_direction) { down_direction_ = down_direction; }
 | 
			
		||||
  void set_left_direction(binary_sensor::BinarySensor *left_direction) { left_direction_ = left_direction; }
 | 
			
		||||
  void set_proximity(sensor::Sensor *proximity) { proximity_ = proximity; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool is_color_enabled_() const;
 | 
			
		||||
  bool is_proximity_enabled_() const;
 | 
			
		||||
@@ -60,6 +50,15 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
  uint8_t gesture_gain_;
 | 
			
		||||
  uint8_t gesture_wait_time_;
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *red_channel_{nullptr};
 | 
			
		||||
  sensor::Sensor *green_channel_{nullptr};
 | 
			
		||||
  sensor::Sensor *blue_channel_{nullptr};
 | 
			
		||||
  sensor::Sensor *clear_channel_{nullptr};
 | 
			
		||||
  binary_sensor::BinarySensor *up_direction_{nullptr};
 | 
			
		||||
  binary_sensor::BinarySensor *right_direction_{nullptr};
 | 
			
		||||
  binary_sensor::BinarySensor *down_direction_{nullptr};
 | 
			
		||||
  binary_sensor::BinarySensor *left_direction_{nullptr};
 | 
			
		||||
  sensor::Sensor *proximity_{nullptr};
 | 
			
		||||
  enum ErrorCode {
 | 
			
		||||
    NONE = 0,
 | 
			
		||||
    COMMUNICATION_FAILED,
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,19 @@ from . import APDS9960, CONF_APDS9960_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["apds9960"]
 | 
			
		||||
 | 
			
		||||
DIRECTIONS = ["up", "down", "left", "right"]
 | 
			
		||||
DIRECTIONS = {
 | 
			
		||||
    "UP": "set_up_direction",
 | 
			
		||||
    "DOWN": "set_down_direction",
 | 
			
		||||
    "LEFT": "set_left_direction",
 | 
			
		||||
    "RIGHT": "set_right_direction",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
 | 
			
		||||
    device_class=DEVICE_CLASS_MOVING
 | 
			
		||||
).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
 | 
			
		||||
        cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, lower=True),
 | 
			
		||||
        cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -21,5 +26,5 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_APDS9960_ID])
 | 
			
		||||
    var = await binary_sensor.new_binary_sensor(config)
 | 
			
		||||
    func = getattr(hub, f"set_{config[CONF_DIRECTION]}_direction_binary_sensor")
 | 
			
		||||
    func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]])
 | 
			
		||||
    cg.add(func(var))
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,13 @@ from . import APDS9960, CONF_APDS9960_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["apds9960"]
 | 
			
		||||
 | 
			
		||||
TYPES = ["clear", "red", "green", "blue", "proximity"]
 | 
			
		||||
TYPES = {
 | 
			
		||||
    "CLEAR": "set_clear_channel",
 | 
			
		||||
    "RED": "set_red_channel",
 | 
			
		||||
    "GREEN": "set_green_channel",
 | 
			
		||||
    "BLUE": "set_blue_channel",
 | 
			
		||||
    "PROXIMITY": "set_proximity",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = sensor.sensor_schema(
 | 
			
		||||
    unit_of_measurement=UNIT_PERCENT,
 | 
			
		||||
@@ -20,7 +26,7 @@ CONFIG_SCHEMA = sensor.sensor_schema(
 | 
			
		||||
    state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_TYPE): cv.one_of(*TYPES, lower=True),
 | 
			
		||||
        cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
 | 
			
		||||
        cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
@@ -29,5 +35,5 @@ CONFIG_SCHEMA = sensor.sensor_schema(
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_APDS9960_ID])
 | 
			
		||||
    var = await sensor.new_sensor(config)
 | 
			
		||||
    func = getattr(hub, f"set_{config[CONF_TYPE]}_sensor")
 | 
			
		||||
    func = getattr(hub, TYPES[config[CONF_TYPE]])
 | 
			
		||||
    cg.add(func(var))
 | 
			
		||||
 
 | 
			
		||||
@@ -56,8 +56,6 @@ service APIConnection {
 | 
			
		||||
  rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
			
		||||
 | 
			
		||||
  rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
 | 
			
		||||
 | 
			
		||||
  rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -208,8 +206,7 @@ message DeviceInfoResponse {
 | 
			
		||||
 | 
			
		||||
  uint32 webserver_port = 10;
 | 
			
		||||
 | 
			
		||||
  uint32 legacy_bluetooth_proxy_version = 11;
 | 
			
		||||
  uint32 bluetooth_proxy_feature_flags = 15;
 | 
			
		||||
  uint32 bluetooth_proxy_version = 11;
 | 
			
		||||
 | 
			
		||||
  string manufacturer = 12;
 | 
			
		||||
 | 
			
		||||
@@ -1133,8 +1130,6 @@ message SubscribeBluetoothLEAdvertisementsRequest {
 | 
			
		||||
  option (id) = 66;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint32 flags = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothServiceData {
 | 
			
		||||
@@ -1159,23 +1154,6 @@ message BluetoothLEAdvertisementResponse {
 | 
			
		||||
  uint32 address_type = 7;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothLERawAdvertisement {
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  sint32 rssi = 2;
 | 
			
		||||
  uint32 address_type = 3;
 | 
			
		||||
 | 
			
		||||
  bytes data = 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothLERawAdvertisementsResponse {
 | 
			
		||||
  option (id) = 93;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  repeated BluetoothLERawAdvertisement advertisements = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum BluetoothDeviceRequestType {
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
 | 
			
		||||
@@ -1419,8 +1397,6 @@ message VoiceAssistantRequest {
 | 
			
		||||
  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
			
		||||
 | 
			
		||||
  bool start = 1;
 | 
			
		||||
  string conversation_id = 2;
 | 
			
		||||
  bool use_vad = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message VoiceAssistantResponse {
 | 
			
		||||
@@ -1457,63 +1433,3 @@ message VoiceAssistantEventResponse {
 | 
			
		||||
  VoiceAssistantEvent event_type = 1;
 | 
			
		||||
  repeated VoiceAssistantEventData data = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== ALARM CONTROL PANEL ====================
 | 
			
		||||
enum AlarmControlPanelState {
 | 
			
		||||
  ALARM_STATE_DISARMED = 0;
 | 
			
		||||
  ALARM_STATE_ARMED_HOME = 1;
 | 
			
		||||
  ALARM_STATE_ARMED_AWAY = 2;
 | 
			
		||||
  ALARM_STATE_ARMED_NIGHT = 3;
 | 
			
		||||
  ALARM_STATE_ARMED_VACATION = 4;
 | 
			
		||||
  ALARM_STATE_ARMED_CUSTOM_BYPASS = 5;
 | 
			
		||||
  ALARM_STATE_PENDING = 6;
 | 
			
		||||
  ALARM_STATE_ARMING = 7;
 | 
			
		||||
  ALARM_STATE_DISARMING = 8;
 | 
			
		||||
  ALARM_STATE_TRIGGERED = 9;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum AlarmControlPanelStateCommand {
 | 
			
		||||
  ALARM_CONTROL_PANEL_DISARM = 0;
 | 
			
		||||
  ALARM_CONTROL_PANEL_ARM_AWAY = 1;
 | 
			
		||||
  ALARM_CONTROL_PANEL_ARM_HOME = 2;
 | 
			
		||||
  ALARM_CONTROL_PANEL_ARM_NIGHT = 3;
 | 
			
		||||
  ALARM_CONTROL_PANEL_ARM_VACATION = 4;
 | 
			
		||||
  ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5;
 | 
			
		||||
  ALARM_CONTROL_PANEL_TRIGGER = 6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message ListEntitiesAlarmControlPanelResponse {
 | 
			
		||||
  option (id) = 94;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_ALARM_CONTROL_PANEL";
 | 
			
		||||
 | 
			
		||||
  string object_id = 1;
 | 
			
		||||
  fixed32 key = 2;
 | 
			
		||||
  string name = 3;
 | 
			
		||||
  string unique_id = 4;
 | 
			
		||||
  string icon = 5;
 | 
			
		||||
  bool disabled_by_default = 6;
 | 
			
		||||
  EntityCategory entity_category = 7;
 | 
			
		||||
  uint32 supported_features = 8;
 | 
			
		||||
  bool requires_code = 9;
 | 
			
		||||
  bool requires_code_to_arm = 10;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message AlarmControlPanelStateResponse {
 | 
			
		||||
  option (id) = 95;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_ALARM_CONTROL_PANEL";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  AlarmControlPanelState state = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message AlarmControlPanelCommandRequest {
 | 
			
		||||
  option (id) = 96;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_ALARM_CONTROL_PANEL";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  AlarmControlPanelStateCommand command = 2;
 | 
			
		||||
  string code = 3;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -51,14 +51,6 @@ void APIConnection::start() {
 | 
			
		||||
  helper_->set_log_info(client_info_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APIConnection::~APIConnection() {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
 | 
			
		||||
    bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIConnection::loop() {
 | 
			
		||||
  if (this->remove_)
 | 
			
		||||
    return;
 | 
			
		||||
@@ -231,7 +223,6 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
 | 
			
		||||
  msg.assumed_state = traits.get_is_assumed_state();
 | 
			
		||||
  msg.supports_position = traits.get_supports_position();
 | 
			
		||||
  msg.supports_tilt = traits.get_supports_tilt();
 | 
			
		||||
  msg.supports_stop = traits.get_supports_stop();
 | 
			
		||||
  msg.device_class = cover->get_device_class();
 | 
			
		||||
  msg.disabled_by_default = cover->is_disabled_by_default();
 | 
			
		||||
  msg.icon = cover->get_icon();
 | 
			
		||||
@@ -853,13 +844,9 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
			
		||||
  bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
			
		||||
  bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
 | 
			
		||||
}
 | 
			
		||||
bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) {
 | 
			
		||||
  if (!this->bluetooth_le_advertisement_subscription_)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) {
 | 
			
		||||
    BluetoothLEAdvertisementResponse resp = msg;
 | 
			
		||||
    for (auto &service : resp.service_data) {
 | 
			
		||||
@@ -907,13 +894,11 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
bool APIConnection::request_voice_assistant(bool start, const std::string &conversation_id, bool use_vad) {
 | 
			
		||||
bool APIConnection::request_voice_assistant(bool start) {
 | 
			
		||||
  if (!this->voice_assistant_subscription_)
 | 
			
		||||
    return false;
 | 
			
		||||
  VoiceAssistantRequest msg;
 | 
			
		||||
  msg.start = start;
 | 
			
		||||
  msg.conversation_id = conversation_id;
 | 
			
		||||
  msg.use_vad = use_vad;
 | 
			
		||||
  return this->send_voice_assistant_request(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
 | 
			
		||||
@@ -932,64 +917,6 @@ void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventR
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
 | 
			
		||||
  if (!this->state_subscription_)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  AlarmControlPanelStateResponse resp{};
 | 
			
		||||
  resp.key = a_alarm_control_panel->get_object_id_hash();
 | 
			
		||||
  resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state());
 | 
			
		||||
  return this->send_alarm_control_panel_state_response(resp);
 | 
			
		||||
}
 | 
			
		||||
bool APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
 | 
			
		||||
  ListEntitiesAlarmControlPanelResponse msg;
 | 
			
		||||
  msg.key = a_alarm_control_panel->get_object_id_hash();
 | 
			
		||||
  msg.object_id = a_alarm_control_panel->get_object_id();
 | 
			
		||||
  msg.name = a_alarm_control_panel->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("alarm_control_panel", a_alarm_control_panel);
 | 
			
		||||
  msg.icon = a_alarm_control_panel->get_icon();
 | 
			
		||||
  msg.disabled_by_default = a_alarm_control_panel->is_disabled_by_default();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(a_alarm_control_panel->get_entity_category());
 | 
			
		||||
  msg.supported_features = a_alarm_control_panel->get_supported_features();
 | 
			
		||||
  msg.requires_code = a_alarm_control_panel->get_requires_code();
 | 
			
		||||
  msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm();
 | 
			
		||||
  return this->send_list_entities_alarm_control_panel_response(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
 | 
			
		||||
  alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key);
 | 
			
		||||
  if (a_alarm_control_panel == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  auto call = a_alarm_control_panel->make_call();
 | 
			
		||||
  switch (msg.command) {
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_DISARM:
 | 
			
		||||
      call.disarm();
 | 
			
		||||
      break;
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
 | 
			
		||||
      call.arm_away();
 | 
			
		||||
      break;
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_ARM_HOME:
 | 
			
		||||
      call.arm_home();
 | 
			
		||||
      break;
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
 | 
			
		||||
      call.arm_night();
 | 
			
		||||
      break;
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
 | 
			
		||||
      call.arm_vacation();
 | 
			
		||||
      break;
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
 | 
			
		||||
      call.arm_custom_bypass();
 | 
			
		||||
      break;
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_TRIGGER:
 | 
			
		||||
      call.pending();
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  call.set_code(msg.code);
 | 
			
		||||
  call.perform();
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
 | 
			
		||||
  if (this->log_subscription_ < level)
 | 
			
		||||
    return false;
 | 
			
		||||
@@ -1014,7 +941,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
 | 
			
		||||
 | 
			
		||||
  HelloResponse resp;
 | 
			
		||||
  resp.api_version_major = 1;
 | 
			
		||||
  resp.api_version_minor = 9;
 | 
			
		||||
  resp.api_version_minor = 8;
 | 
			
		||||
  resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
 | 
			
		||||
  resp.name = App.get_name();
 | 
			
		||||
 | 
			
		||||
@@ -1051,8 +978,6 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
 | 
			
		||||
  resp.manufacturer = "Espressif";
 | 
			
		||||
#elif defined(USE_RP2040)
 | 
			
		||||
  resp.manufacturer = "Raspberry Pi";
 | 
			
		||||
#elif defined(USE_HOST)
 | 
			
		||||
  resp.manufacturer = "Host";
 | 
			
		||||
#endif
 | 
			
		||||
  resp.model = ESPHOME_BOARD;
 | 
			
		||||
#ifdef USE_DEEP_SLEEP
 | 
			
		||||
@@ -1066,11 +991,12 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
 | 
			
		||||
  resp.webserver_port = USE_WEBSERVER_PORT;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  resp.legacy_bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->get_legacy_version();
 | 
			
		||||
  resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
 | 
			
		||||
  resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active()
 | 
			
		||||
                                     ? bluetooth_proxy::ACTIVE_CONNECTIONS_VERSION
 | 
			
		||||
                                     : bluetooth_proxy::PASSIVE_ONLY_VERSION;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  resp.voice_assistant_version = voice_assistant::global_voice_assistant->get_version();
 | 
			
		||||
  resp.voice_assistant_version = 1;
 | 
			
		||||
#endif
 | 
			
		||||
  return resp;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ namespace api {
 | 
			
		||||
class APIConnection : public APIServerConnection {
 | 
			
		||||
 public:
 | 
			
		||||
  APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
 | 
			
		||||
  virtual ~APIConnection();
 | 
			
		||||
  virtual ~APIConnection() = default;
 | 
			
		||||
 | 
			
		||||
  void start();
 | 
			
		||||
  void loop();
 | 
			
		||||
@@ -98,8 +98,12 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
    this->send_homeassistant_service_response(call);
 | 
			
		||||
  }
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
			
		||||
  void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
			
		||||
  void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override {
 | 
			
		||||
    this->bluetooth_le_advertisement_subscription_ = true;
 | 
			
		||||
  }
 | 
			
		||||
  void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override {
 | 
			
		||||
    this->bluetooth_le_advertisement_subscription_ = false;
 | 
			
		||||
  }
 | 
			
		||||
  bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg);
 | 
			
		||||
 | 
			
		||||
  void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
 | 
			
		||||
@@ -124,17 +128,11 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override {
 | 
			
		||||
    this->voice_assistant_subscription_ = msg.subscribe;
 | 
			
		||||
  }
 | 
			
		||||
  bool request_voice_assistant(bool start, const std::string &conversation_id, bool use_vad);
 | 
			
		||||
  bool request_voice_assistant(bool start);
 | 
			
		||||
  void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
 | 
			
		||||
  void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
  bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
			
		||||
  bool send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
			
		||||
  void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  void on_disconnect_response(const DisconnectResponse &value) override;
 | 
			
		||||
  void on_ping_response(const PingResponse &value) override {
 | 
			
		||||
    // we initiated ping
 | 
			
		||||
@@ -213,6 +211,9 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  uint32_t last_traffic_;
 | 
			
		||||
  bool sent_ping_{false};
 | 
			
		||||
  bool service_call_subscription_{false};
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool bluetooth_le_advertisement_subscription_{false};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  bool voice_assistant_subscription_{false};
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -433,57 +433,6 @@ template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::V
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::AlarmControlPanelState>(enums::AlarmControlPanelState value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::ALARM_STATE_DISARMED:
 | 
			
		||||
      return "ALARM_STATE_DISARMED";
 | 
			
		||||
    case enums::ALARM_STATE_ARMED_HOME:
 | 
			
		||||
      return "ALARM_STATE_ARMED_HOME";
 | 
			
		||||
    case enums::ALARM_STATE_ARMED_AWAY:
 | 
			
		||||
      return "ALARM_STATE_ARMED_AWAY";
 | 
			
		||||
    case enums::ALARM_STATE_ARMED_NIGHT:
 | 
			
		||||
      return "ALARM_STATE_ARMED_NIGHT";
 | 
			
		||||
    case enums::ALARM_STATE_ARMED_VACATION:
 | 
			
		||||
      return "ALARM_STATE_ARMED_VACATION";
 | 
			
		||||
    case enums::ALARM_STATE_ARMED_CUSTOM_BYPASS:
 | 
			
		||||
      return "ALARM_STATE_ARMED_CUSTOM_BYPASS";
 | 
			
		||||
    case enums::ALARM_STATE_PENDING:
 | 
			
		||||
      return "ALARM_STATE_PENDING";
 | 
			
		||||
    case enums::ALARM_STATE_ARMING:
 | 
			
		||||
      return "ALARM_STATE_ARMING";
 | 
			
		||||
    case enums::ALARM_STATE_DISARMING:
 | 
			
		||||
      return "ALARM_STATE_DISARMING";
 | 
			
		||||
    case enums::ALARM_STATE_TRIGGERED:
 | 
			
		||||
      return "ALARM_STATE_TRIGGERED";
 | 
			
		||||
    default:
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<>
 | 
			
		||||
const char *proto_enum_to_string<enums::AlarmControlPanelStateCommand>(enums::AlarmControlPanelStateCommand value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_DISARM:
 | 
			
		||||
      return "ALARM_CONTROL_PANEL_DISARM";
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
 | 
			
		||||
      return "ALARM_CONTROL_PANEL_ARM_AWAY";
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_ARM_HOME:
 | 
			
		||||
      return "ALARM_CONTROL_PANEL_ARM_HOME";
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
 | 
			
		||||
      return "ALARM_CONTROL_PANEL_ARM_NIGHT";
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
 | 
			
		||||
      return "ALARM_CONTROL_PANEL_ARM_VACATION";
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
 | 
			
		||||
      return "ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS";
 | 
			
		||||
    case enums::ALARM_CONTROL_PANEL_TRIGGER:
 | 
			
		||||
      return "ALARM_CONTROL_PANEL_TRIGGER";
 | 
			
		||||
    default:
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
@@ -668,11 +617,7 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 11: {
 | 
			
		||||
      this->legacy_bluetooth_proxy_version = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 15: {
 | 
			
		||||
      this->bluetooth_proxy_feature_flags = value.as_uint32();
 | 
			
		||||
      this->bluetooth_proxy_version = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 14: {
 | 
			
		||||
@@ -736,8 +681,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(8, this->project_name);
 | 
			
		||||
  buffer.encode_string(9, this->project_version);
 | 
			
		||||
  buffer.encode_uint32(10, this->webserver_port);
 | 
			
		||||
  buffer.encode_uint32(11, this->legacy_bluetooth_proxy_version);
 | 
			
		||||
  buffer.encode_uint32(15, this->bluetooth_proxy_feature_flags);
 | 
			
		||||
  buffer.encode_uint32(11, this->bluetooth_proxy_version);
 | 
			
		||||
  buffer.encode_string(12, this->manufacturer);
 | 
			
		||||
  buffer.encode_string(13, this->friendly_name);
 | 
			
		||||
  buffer.encode_uint32(14, this->voice_assistant_version);
 | 
			
		||||
@@ -787,13 +731,8 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  legacy_bluetooth_proxy_version: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->legacy_bluetooth_proxy_version);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  bluetooth_proxy_feature_flags: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->bluetooth_proxy_feature_flags);
 | 
			
		||||
  out.append("  bluetooth_proxy_version: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->bluetooth_proxy_version);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
@@ -5102,28 +5041,10 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->flags = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void SubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint32(1, this->flags);
 | 
			
		||||
}
 | 
			
		||||
void SubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("SubscribeBluetoothLEAdvertisementsRequest {\n");
 | 
			
		||||
  out.append("  flags: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->flags);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
  out.append("SubscribeBluetoothLEAdvertisementsRequest {}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
@@ -5276,92 +5197,6 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothLERawAdvertisement::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->rssi = value.as_sint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->address_type = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool BluetoothLERawAdvertisement::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->data = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_sint32(2, this->rssi);
 | 
			
		||||
  buffer.encode_uint32(3, this->address_type);
 | 
			
		||||
  buffer.encode_string(4, this->data);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothLERawAdvertisement::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothLERawAdvertisement {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  rssi: ");
 | 
			
		||||
  sprintf(buffer, "%d", this->rssi);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  address_type: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->address_type);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  data: ");
 | 
			
		||||
  out.append("'").append(this->data).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothLERawAdvertisementsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->advertisements.push_back(value.as_message<BluetoothLERawAdvertisement>());
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  for (auto &it : this->advertisements) {
 | 
			
		||||
    buffer.encode_message<BluetoothLERawAdvertisement>(1, it, true);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothLERawAdvertisementsResponse {\n");
 | 
			
		||||
  for (const auto &it : this->advertisements) {
 | 
			
		||||
    out.append("  advertisements: ");
 | 
			
		||||
    it.dump_to(out);
 | 
			
		||||
    out.append("\n");
 | 
			
		||||
  }
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
@@ -6348,29 +6183,11 @@ bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
 | 
			
		||||
      this->start = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->use_vad = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->conversation_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_bool(1, this->start);
 | 
			
		||||
  buffer.encode_string(2, this->conversation_id);
 | 
			
		||||
  buffer.encode_bool(3, this->use_vad);
 | 
			
		||||
}
 | 
			
		||||
void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->start); }
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void VoiceAssistantRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
@@ -6378,14 +6195,6 @@ void VoiceAssistantRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  start: ");
 | 
			
		||||
  out.append(YESNO(this->start));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  conversation_id: ");
 | 
			
		||||
  out.append("'").append(this->conversation_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  use_vad: ");
 | 
			
		||||
  out.append(YESNO(this->use_vad));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -6496,217 +6305,6 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 6: {
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
      this->supported_features = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 9: {
 | 
			
		||||
      this->requires_code = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 10: {
 | 
			
		||||
      this->requires_code_to_arm = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ListEntitiesAlarmControlPanelResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->object_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->name = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->unique_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 5: {
 | 
			
		||||
      this->icon = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ListEntitiesAlarmControlPanelResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(1, this->object_id);
 | 
			
		||||
  buffer.encode_fixed32(2, this->key);
 | 
			
		||||
  buffer.encode_string(3, this->name);
 | 
			
		||||
  buffer.encode_string(4, this->unique_id);
 | 
			
		||||
  buffer.encode_string(5, this->icon);
 | 
			
		||||
  buffer.encode_bool(6, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
 | 
			
		||||
  buffer.encode_uint32(8, this->supported_features);
 | 
			
		||||
  buffer.encode_bool(9, this->requires_code);
 | 
			
		||||
  buffer.encode_bool(10, this->requires_code_to_arm);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesAlarmControlPanelResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  name: ");
 | 
			
		||||
  out.append("'").append(this->name).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  unique_id: ");
 | 
			
		||||
  out.append("'").append(this->unique_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  icon: ");
 | 
			
		||||
  out.append("'").append(this->icon).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  disabled_by_default: ");
 | 
			
		||||
  out.append(YESNO(this->disabled_by_default));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  supported_features: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->supported_features);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  requires_code: ");
 | 
			
		||||
  out.append(YESNO(this->requires_code));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  requires_code_to_arm: ");
 | 
			
		||||
  out.append(YESNO(this->requires_code_to_arm));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool AlarmControlPanelStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->state = value.as_enum<enums::AlarmControlPanelState>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool AlarmControlPanelStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_fixed32(1, this->key);
 | 
			
		||||
  buffer.encode_enum<enums::AlarmControlPanelState>(2, this->state);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void AlarmControlPanelStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("AlarmControlPanelStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  state: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::AlarmControlPanelState>(this->state));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->command = value.as_enum<enums::AlarmControlPanelStateCommand>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool AlarmControlPanelCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->code = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool AlarmControlPanelCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void AlarmControlPanelCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_fixed32(1, this->key);
 | 
			
		||||
  buffer.encode_enum<enums::AlarmControlPanelStateCommand>(2, this->command);
 | 
			
		||||
  buffer.encode_string(3, this->code);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("AlarmControlPanelCommandRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  command: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::AlarmControlPanelStateCommand>(this->command));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  code: ");
 | 
			
		||||
  out.append("'").append(this->code).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -176,27 +176,6 @@ enum VoiceAssistantEvent : uint32_t {
 | 
			
		||||
  VOICE_ASSISTANT_TTS_START = 7,
 | 
			
		||||
  VOICE_ASSISTANT_TTS_END = 8,
 | 
			
		||||
};
 | 
			
		||||
enum AlarmControlPanelState : uint32_t {
 | 
			
		||||
  ALARM_STATE_DISARMED = 0,
 | 
			
		||||
  ALARM_STATE_ARMED_HOME = 1,
 | 
			
		||||
  ALARM_STATE_ARMED_AWAY = 2,
 | 
			
		||||
  ALARM_STATE_ARMED_NIGHT = 3,
 | 
			
		||||
  ALARM_STATE_ARMED_VACATION = 4,
 | 
			
		||||
  ALARM_STATE_ARMED_CUSTOM_BYPASS = 5,
 | 
			
		||||
  ALARM_STATE_PENDING = 6,
 | 
			
		||||
  ALARM_STATE_ARMING = 7,
 | 
			
		||||
  ALARM_STATE_DISARMING = 8,
 | 
			
		||||
  ALARM_STATE_TRIGGERED = 9,
 | 
			
		||||
};
 | 
			
		||||
enum AlarmControlPanelStateCommand : uint32_t {
 | 
			
		||||
  ALARM_CONTROL_PANEL_DISARM = 0,
 | 
			
		||||
  ALARM_CONTROL_PANEL_ARM_AWAY = 1,
 | 
			
		||||
  ALARM_CONTROL_PANEL_ARM_HOME = 2,
 | 
			
		||||
  ALARM_CONTROL_PANEL_ARM_NIGHT = 3,
 | 
			
		||||
  ALARM_CONTROL_PANEL_ARM_VACATION = 4,
 | 
			
		||||
  ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5,
 | 
			
		||||
  ALARM_CONTROL_PANEL_TRIGGER = 6,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace enums
 | 
			
		||||
 | 
			
		||||
@@ -308,8 +287,7 @@ class DeviceInfoResponse : public ProtoMessage {
 | 
			
		||||
  std::string project_name{};
 | 
			
		||||
  std::string project_version{};
 | 
			
		||||
  uint32_t webserver_port{0};
 | 
			
		||||
  uint32_t legacy_bluetooth_proxy_version{0};
 | 
			
		||||
  uint32_t bluetooth_proxy_feature_flags{0};
 | 
			
		||||
  uint32_t bluetooth_proxy_version{0};
 | 
			
		||||
  std::string manufacturer{};
 | 
			
		||||
  std::string friendly_name{};
 | 
			
		||||
  uint32_t voice_assistant_version{0};
 | 
			
		||||
@@ -1269,14 +1247,12 @@ class MediaPlayerCommandRequest : public ProtoMessage {
 | 
			
		||||
};
 | 
			
		||||
class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t flags{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothServiceData : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
@@ -1310,32 +1286,6 @@ class BluetoothLEAdvertisementResponse : public ProtoMessage {
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothLERawAdvertisement : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  int32_t rssi{0};
 | 
			
		||||
  uint32_t address_type{0};
 | 
			
		||||
  std::string data{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothLERawAdvertisementsResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::vector<BluetoothLERawAdvertisement> advertisements{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothDeviceRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
@@ -1654,15 +1604,12 @@ class SubscribeVoiceAssistantRequest : public ProtoMessage {
 | 
			
		||||
class VoiceAssistantRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  bool start{false};
 | 
			
		||||
  std::string conversation_id{};
 | 
			
		||||
  bool use_vad{false};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class VoiceAssistantResponse : public ProtoMessage {
 | 
			
		||||
@@ -1702,56 +1649,6 @@ class VoiceAssistantEventResponse : public ProtoMessage {
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class ListEntitiesAlarmControlPanelResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::string object_id{};
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  std::string name{};
 | 
			
		||||
  std::string unique_id{};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  uint32_t supported_features{0};
 | 
			
		||||
  bool requires_code{false};
 | 
			
		||||
  bool requires_code_to_arm{false};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class AlarmControlPanelStateResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  enums::AlarmControlPanelState state{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class AlarmControlPanelCommandRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  enums::AlarmControlPanelStateCommand command{};
 | 
			
		||||
  std::string code{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -339,15 +339,6 @@ bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const Blu
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_le_raw_advertisements_response(
 | 
			
		||||
    const BluetoothLERawAdvertisementsResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_le_raw_advertisements_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothLERawAdvertisementsResponse>(msg, 93);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg) {
 | 
			
		||||
@@ -476,25 +467,6 @@ bool APIServerConnectionBase::send_voice_assistant_request(const VoiceAssistantR
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response(
 | 
			
		||||
    const ListEntitiesAlarmControlPanelResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_list_entities_alarm_control_panel_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<ListEntitiesAlarmControlPanelResponse>(msg, 94);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
bool APIServerConnectionBase::send_alarm_control_panel_state_response(const AlarmControlPanelStateResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_alarm_control_panel_state_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<AlarmControlPanelStateResponse>(msg, 95);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
#endif
 | 
			
		||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
 | 
			
		||||
  switch (msg_type) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
@@ -902,17 +874,6 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
			
		||||
      ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_voice_assistant_event_response(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 96: {
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
      AlarmControlPanelCommandRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_alarm_control_panel_command_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_alarm_control_panel_command_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -1325,19 +1286,6 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo
 | 
			
		||||
  this->subscribe_voice_assistant(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->alarm_control_panel_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -161,9 +161,6 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_le_raw_advertisements_response(const BluetoothLERawAdvertisementsResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
@@ -239,15 +236,6 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
  bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
  bool send_alarm_control_panel_state_response(const AlarmControlPanelStateResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
  virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
 | 
			
		||||
@@ -333,9 +321,6 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
  virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  void on_hello_request(const HelloRequest &msg) override;
 | 
			
		||||
@@ -417,9 +402,6 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
  void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
 
 | 
			
		||||
@@ -291,7 +291,112 @@ void APIServer::send_homeassistant_service_call(const HomeassistantServiceRespon
 | 
			
		||||
    client->send_homeassistant_service_call(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServer::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_le_advertisement(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
 | 
			
		||||
  BluetoothDeviceConnectionResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.connected = connected;
 | 
			
		||||
  call.mtu = mtu;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_device_connection_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error) {
 | 
			
		||||
  BluetoothDevicePairingResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.paired = paired;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_device_pairing_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error) {
 | 
			
		||||
  BluetoothDeviceUnpairingResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.success = success;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_device_unpairing_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error) {
 | 
			
		||||
  BluetoothDeviceClearCacheResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.success = success;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_device_clear_cache_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) {
 | 
			
		||||
  BluetoothConnectionsFreeResponse call;
 | 
			
		||||
  call.free = free;
 | 
			
		||||
  call.limit = limit;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_connections_free_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_read_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_write_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_notify_data_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_notify_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_get_services_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_services_done(uint64_t address) {
 | 
			
		||||
  BluetoothGATTGetServicesDoneResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_get_services_done_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
 | 
			
		||||
  BluetoothGATTErrorResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.handle = handle;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_error_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
APIServer::APIServer() { global_api_server = this; }
 | 
			
		||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
 | 
			
		||||
                                               std::function<void(std::string)> f) {
 | 
			
		||||
@@ -323,29 +428,20 @@ void APIServer::on_shutdown() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
bool APIServer::start_voice_assistant(const std::string &conversation_id, bool use_vad) {
 | 
			
		||||
bool APIServer::start_voice_assistant() {
 | 
			
		||||
  for (auto &c : this->clients_) {
 | 
			
		||||
    if (c->request_voice_assistant(true, conversation_id, use_vad))
 | 
			
		||||
    if (c->request_voice_assistant(true))
 | 
			
		||||
      return true;
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
void APIServer::stop_voice_assistant() {
 | 
			
		||||
  for (auto &c : this->clients_) {
 | 
			
		||||
    if (c->request_voice_assistant(false, "", false))
 | 
			
		||||
    if (c->request_voice_assistant(false))
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
 | 
			
		||||
  if (obj->is_internal())
 | 
			
		||||
    return;
 | 
			
		||||
  for (auto &c : this->clients_)
 | 
			
		||||
    c->send_alarm_control_panel_state(obj);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -75,20 +75,31 @@ class APIServer : public Component, public Controller {
 | 
			
		||||
  void on_media_player_update(media_player::MediaPlayer *obj) override;
 | 
			
		||||
#endif
 | 
			
		||||
  void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call);
 | 
			
		||||
  void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_bluetooth_connections_free(uint8_t free, uint8_t limit);
 | 
			
		||||
  void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_services_done(uint64_t address);
 | 
			
		||||
  void send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error);
 | 
			
		||||
#endif
 | 
			
		||||
  void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
 | 
			
		||||
#ifdef USE_HOMEASSISTANT_TIME
 | 
			
		||||
  void request_time();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  bool start_voice_assistant(const std::string &conversation_id, bool use_vad);
 | 
			
		||||
  bool start_voice_assistant();
 | 
			
		||||
  void stop_voice_assistant();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
  void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  bool is_connected() const;
 | 
			
		||||
 | 
			
		||||
  struct HomeAssistantStateSubscription {
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ async def async_run_logs(config, address):
 | 
			
		||||
        except APIConnectionError:
 | 
			
		||||
            cli.disconnect()
 | 
			
		||||
 | 
			
		||||
    async def on_disconnect(expected_disconnect: bool) -> None:
 | 
			
		||||
    async def on_disconnect():
 | 
			
		||||
        _LOGGER.warning("Disconnected from API")
 | 
			
		||||
 | 
			
		||||
    zc = zeroconf.Zeroconf()
 | 
			
		||||
 
 | 
			
		||||
@@ -69,11 +69,6 @@ bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_play
 | 
			
		||||
  return this->client_->send_media_player_info(media_player);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
 | 
			
		||||
  return this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -54,9 +54,6 @@ class ListEntitiesIterator : public ComponentIterator {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  bool on_media_player(media_player::MediaPlayer *media_player) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
  bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
 | 
			
		||||
#endif
 | 
			
		||||
  bool on_end() override;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -55,11 +55,6 @@ bool InitialStateIterator::on_media_player(media_player::MediaPlayer *media_play
 | 
			
		||||
  return this->client_->send_media_player_state(media_player);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
 | 
			
		||||
  return this->client_->send_alarm_control_panel_state(a_alarm_control_panel);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
 
 | 
			
		||||
@@ -51,9 +51,6 @@ class InitialStateIterator : public ComponentIterator {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  bool on_media_player(media_player::MediaPlayer *media_player) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
  bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  APIConnection *client_;
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
CODEOWNERS = ["@danieltwagner"]
 | 
			
		||||
@@ -1,235 +0,0 @@
 | 
			
		||||
#include "atm90e26.h"
 | 
			
		||||
#include "atm90e26_reg.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atm90e26 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "atm90e26";
 | 
			
		||||
 | 
			
		||||
void ATM90E26Component::update() {
 | 
			
		||||
  if (this->read16_(ATM90E26_REGISTER_FUNCEN) != 0x0030) {
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->voltage_sensor_ != nullptr) {
 | 
			
		||||
    this->voltage_sensor_->publish_state(this->get_line_voltage_());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->current_sensor_ != nullptr) {
 | 
			
		||||
    this->current_sensor_->publish_state(this->get_line_current_());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->power_sensor_ != nullptr) {
 | 
			
		||||
    this->power_sensor_->publish_state(this->get_active_power_());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->reactive_power_sensor_ != nullptr) {
 | 
			
		||||
    this->reactive_power_sensor_->publish_state(this->get_reactive_power_());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->power_factor_sensor_ != nullptr) {
 | 
			
		||||
    this->power_factor_sensor_->publish_state(this->get_power_factor_());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->forward_active_energy_sensor_ != nullptr) {
 | 
			
		||||
    this->forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->reverse_active_energy_sensor_ != nullptr) {
 | 
			
		||||
    this->reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->freq_sensor_ != nullptr) {
 | 
			
		||||
    this->freq_sensor_->publish_state(this->get_frequency_());
 | 
			
		||||
  }
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E26Component::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up ATM90E26 Component...");
 | 
			
		||||
  this->spi_setup();
 | 
			
		||||
 | 
			
		||||
  uint16_t mmode = 0x422;  // default values for everything but L/N line current gains
 | 
			
		||||
  mmode |= (gain_pga_ & 0x7) << 13;
 | 
			
		||||
  mmode |= (n_line_gain_ & 0x3) << 11;
 | 
			
		||||
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_SOFTRESET, 0x789A);  // Perform soft reset
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_FUNCEN,
 | 
			
		||||
                 0x0030);  // Voltage sag irq=1, report on warnout pin=1, energy dir change irq=0
 | 
			
		||||
  uint16_t read = this->read16_(ATM90E26_REGISTER_LASTDATA);
 | 
			
		||||
  if (read != 0x0030) {
 | 
			
		||||
    ESP_LOGW(TAG, "Could not initialize ATM90E26 IC, check SPI settings: %d", read);
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // TODO: 100 * <nominal voltage, e.g. 230> * sqrt(2) * <fraction of nominal, e.g. 0.9> / (4 * gain_voltage/32768)
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_SAGTH, 0x17DD);  // Voltage sag threshhold 0x1F2F
 | 
			
		||||
 | 
			
		||||
  // Set metering calibration values
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_CALSTART, 0x5678);  // CAL Metering calibration startup command
 | 
			
		||||
 | 
			
		||||
  // Configure
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_MMODE, mmode);  // Metering Mode Configuration (see above)
 | 
			
		||||
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_PLCONSTH, (pl_const_ >> 16));   // PL Constant MSB
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_PLCONSTL, pl_const_ & 0xFFFF);  // PL Constant LSB
 | 
			
		||||
 | 
			
		||||
  // Calibrate this to be 1 pulse per Wh
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_LGAIN, gain_metering_);  // L Line Calibration Gain (active power metering)
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_LPHI, 0x0000);           // L Line Calibration Angle
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_NGAIN, 0x0000);          // N Line Calibration Gain
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_NPHI, 0x0000);           // N Line Calibration Angle
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_PSTARTTH, 0x08BD);       // Active Startup Power Threshold (default) = 2237
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_PNOLTH, 0x0000);         // Active No-Load Power Threshold
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_QSTARTTH, 0x0AEC);       // Reactive Startup Power Threshold (default) = 2796
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_QNOLTH, 0x0000);         // Reactive No-Load Power Threshold
 | 
			
		||||
 | 
			
		||||
  // Compute Checksum for the registers we set above
 | 
			
		||||
  // low byte = sum of all bytes
 | 
			
		||||
  uint16_t cs =
 | 
			
		||||
      ((mmode >> 8) + (mmode & 0xFF) + (pl_const_ >> 24) + ((pl_const_ >> 16) & 0xFF) + ((pl_const_ >> 8) & 0xFF) +
 | 
			
		||||
       (pl_const_ & 0xFF) + (gain_metering_ >> 8) + (gain_metering_ & 0xFF) + 0x08 + 0xBD + 0x0A + 0xEC) &
 | 
			
		||||
      0xFF;
 | 
			
		||||
  // high byte = XOR of all bytes
 | 
			
		||||
  cs |= ((mmode >> 8) ^ (mmode & 0xFF) ^ (pl_const_ >> 24) ^ ((pl_const_ >> 16) & 0xFF) ^ ((pl_const_ >> 8) & 0xFF) ^
 | 
			
		||||
         (pl_const_ & 0xFF) ^ (gain_metering_ >> 8) ^ (gain_metering_ & 0xFF) ^ 0x08 ^ 0xBD ^ 0x0A ^ 0xEC)
 | 
			
		||||
        << 8;
 | 
			
		||||
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_CS1, cs);
 | 
			
		||||
  ESP_LOGVV(TAG, "Set CS1 to: 0x%04X", cs);
 | 
			
		||||
 | 
			
		||||
  // Set measurement calibration values
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_ADJSTART, 0x5678);      // Measurement calibration startup command, registers 31-3A
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_UGAIN, gain_voltage_);  // Voltage RMS gain
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_IGAINL, gain_ct_);      // L line current RMS gain
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_IGAINN, 0x7530);        // N Line Current RMS Gain
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_UOFFSET, 0x0000);       // Voltage Offset
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_IOFFSETL, 0x0000);      // L Line Current Offset
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_IOFFSETN, 0x0000);      // N Line Current Offse
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_POFFSETL, 0x0000);      // L Line Active Power Offset
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_QOFFSETL, 0x0000);      // L Line Reactive Power Offset
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_POFFSETN, 0x0000);      // N Line Active Power Offset
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_QOFFSETN, 0x0000);      // N Line Reactive Power Offset
 | 
			
		||||
 | 
			
		||||
  // Compute Checksum for the registers we set above
 | 
			
		||||
  cs = ((gain_voltage_ >> 8) + (gain_voltage_ & 0xFF) + (gain_ct_ >> 8) + (gain_ct_ & 0xFF) + 0x75 + 0x30) & 0xFF;
 | 
			
		||||
  cs |= ((gain_voltage_ >> 8) ^ (gain_voltage_ & 0xFF) ^ (gain_ct_ >> 8) ^ (gain_ct_ & 0xFF) ^ 0x75 ^ 0x30) << 8;
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_CS2, cs);
 | 
			
		||||
  ESP_LOGVV(TAG, "Set CS2 to: 0x%04X", cs);
 | 
			
		||||
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_CALSTART,
 | 
			
		||||
                 0x8765);  // Checks correctness of 21-2B registers and starts normal metering if ok
 | 
			
		||||
  this->write16_(ATM90E26_REGISTER_ADJSTART,
 | 
			
		||||
                 0x8765);  // Checks correctness of 31-3A registers and starts normal measurement  if ok
 | 
			
		||||
 | 
			
		||||
  uint16_t sys_status = this->read16_(ATM90E26_REGISTER_SYSSTATUS);
 | 
			
		||||
  if (sys_status & 0xC000) {  // Checksum 1 Error
 | 
			
		||||
 | 
			
		||||
    ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS1 was incorrect, expected: 0x%04X",
 | 
			
		||||
             this->read16_(ATM90E26_REGISTER_CS1));
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
  }
 | 
			
		||||
  if (sys_status & 0x3000) {  // Checksum 2 Error
 | 
			
		||||
    ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS2 was incorrect, expected: 0x%04X",
 | 
			
		||||
             this->read16_(ATM90E26_REGISTER_CS2));
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E26Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG("", "ATM90E26:");
 | 
			
		||||
  LOG_PIN("  CS Pin: ", this->cs_);
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Communication with ATM90E26 failed!");
 | 
			
		||||
  }
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
  LOG_SENSOR("  ", "Voltage A", this->voltage_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Current A", this->current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Power A", this->power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Reactive Power A", this->reactive_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "PF A", this->power_factor_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Forward Energy A", this->forward_active_energy_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Reverse Energy A", this->reverse_active_energy_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Frequency", this->freq_sensor_);
 | 
			
		||||
}
 | 
			
		||||
float ATM90E26Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
uint16_t ATM90E26Component::read16_(uint8_t a_register) {
 | 
			
		||||
  uint8_t data[2];
 | 
			
		||||
  uint16_t output;
 | 
			
		||||
 | 
			
		||||
  this->enable();
 | 
			
		||||
  delayMicroseconds(4);
 | 
			
		||||
  this->write_byte(a_register | 0x80);
 | 
			
		||||
  delayMicroseconds(4);
 | 
			
		||||
  this->read_array(data, 2);
 | 
			
		||||
  this->disable();
 | 
			
		||||
 | 
			
		||||
  output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
 | 
			
		||||
  ESP_LOGVV(TAG, "read16_ 0x%04X output 0x%04X", a_register, output);
 | 
			
		||||
  return output;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E26Component::write16_(uint8_t a_register, uint16_t val) {
 | 
			
		||||
  ESP_LOGVV(TAG, "write16_ 0x%04X val 0x%04X", a_register, val);
 | 
			
		||||
  this->enable();
 | 
			
		||||
  delayMicroseconds(4);
 | 
			
		||||
  this->write_byte(a_register & 0x7F);
 | 
			
		||||
  delayMicroseconds(4);
 | 
			
		||||
  this->write_byte((val >> 8) & 0xFF);
 | 
			
		||||
  this->write_byte(val & 0xFF);
 | 
			
		||||
  this->disable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E26Component::get_line_current_() {
 | 
			
		||||
  uint16_t current = this->read16_(ATM90E26_REGISTER_IRMS);
 | 
			
		||||
  return current / 1000.0f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E26Component::get_line_voltage_() {
 | 
			
		||||
  uint16_t voltage = this->read16_(ATM90E26_REGISTER_URMS);
 | 
			
		||||
  return voltage / 100.0f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E26Component::get_active_power_() {
 | 
			
		||||
  int16_t val = this->read16_(ATM90E26_REGISTER_PMEAN);  // two's complement
 | 
			
		||||
  return (float) val;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E26Component::get_reactive_power_() {
 | 
			
		||||
  int16_t val = this->read16_(ATM90E26_REGISTER_QMEAN);  // two's complement
 | 
			
		||||
  return (float) val;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E26Component::get_power_factor_() {
 | 
			
		||||
  uint16_t val = this->read16_(ATM90E26_REGISTER_POWERF);  // signed
 | 
			
		||||
  if (val & 0x8000) {
 | 
			
		||||
    return -(val & 0x7FF) / 1000.0f;
 | 
			
		||||
  } else {
 | 
			
		||||
    return val / 1000.0f;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E26Component::get_forward_active_energy_() {
 | 
			
		||||
  uint16_t val = this->read16_(ATM90E26_REGISTER_APENERGY);
 | 
			
		||||
  if ((UINT32_MAX - this->cumulative_forward_active_energy_) > val) {
 | 
			
		||||
    this->cumulative_forward_active_energy_ += val;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->cumulative_forward_active_energy_ = val;
 | 
			
		||||
  }
 | 
			
		||||
  // The register holds thenths of pulses, we want to output Wh
 | 
			
		||||
  return (this->cumulative_forward_active_energy_ * 100.0f / meter_constant_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E26Component::get_reverse_active_energy_() {
 | 
			
		||||
  uint16_t val = this->read16_(ATM90E26_REGISTER_ANENERGY);
 | 
			
		||||
  if (UINT32_MAX - this->cumulative_reverse_active_energy_ > val) {
 | 
			
		||||
    this->cumulative_reverse_active_energy_ += val;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->cumulative_reverse_active_energy_ = val;
 | 
			
		||||
  }
 | 
			
		||||
  return (this->cumulative_reverse_active_energy_ * 100.0f / meter_constant_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E26Component::get_frequency_() {
 | 
			
		||||
  uint16_t freq = this->read16_(ATM90E26_REGISTER_FREQ);
 | 
			
		||||
  return freq / 100.0f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace atm90e26
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,72 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/spi/spi.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atm90e26 {
 | 
			
		||||
 | 
			
		||||
class ATM90E26Component : public PollingComponent,
 | 
			
		||||
                          public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
 | 
			
		||||
                                                spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
  void set_voltage_sensor(sensor::Sensor *obj) { this->voltage_sensor_ = obj; }
 | 
			
		||||
  void set_current_sensor(sensor::Sensor *obj) { this->current_sensor_ = obj; }
 | 
			
		||||
  void set_power_sensor(sensor::Sensor *obj) { this->power_sensor_ = obj; }
 | 
			
		||||
  void set_reactive_power_sensor(sensor::Sensor *obj) { this->reactive_power_sensor_ = obj; }
 | 
			
		||||
  void set_forward_active_energy_sensor(sensor::Sensor *obj) { this->forward_active_energy_sensor_ = obj; }
 | 
			
		||||
  void set_reverse_active_energy_sensor(sensor::Sensor *obj) { this->reverse_active_energy_sensor_ = obj; }
 | 
			
		||||
  void set_power_factor_sensor(sensor::Sensor *obj) { this->power_factor_sensor_ = obj; }
 | 
			
		||||
  void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
 | 
			
		||||
  void set_line_freq(int freq) { line_freq_ = freq; }
 | 
			
		||||
  void set_meter_constant(float val) { meter_constant_ = val; }
 | 
			
		||||
  void set_pl_const(uint32_t pl_const) { pl_const_ = pl_const; }
 | 
			
		||||
  void set_gain_metering(uint16_t gain) { this->gain_metering_ = gain; }
 | 
			
		||||
  void set_gain_voltage(uint16_t gain) { this->gain_voltage_ = gain; }
 | 
			
		||||
  void set_gain_ct(uint16_t gain) { this->gain_ct_ = gain; }
 | 
			
		||||
  void set_gain_pga(uint16_t gain) { gain_pga_ = gain; }
 | 
			
		||||
  void set_n_line_gain(uint16_t gain) { n_line_gain_ = gain; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint16_t read16_(uint8_t a_register);
 | 
			
		||||
  int read32_(uint8_t addr_h, uint8_t addr_l);
 | 
			
		||||
  void write16_(uint8_t a_register, uint16_t val);
 | 
			
		||||
 | 
			
		||||
  float get_line_voltage_();
 | 
			
		||||
  float get_line_current_();
 | 
			
		||||
  float get_active_power_();
 | 
			
		||||
  float get_reactive_power_();
 | 
			
		||||
  float get_power_factor_();
 | 
			
		||||
  float get_forward_active_energy_();
 | 
			
		||||
  float get_reverse_active_energy_();
 | 
			
		||||
  float get_frequency_();
 | 
			
		||||
  float get_chip_temperature_();
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *freq_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *voltage_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *current_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *power_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *reactive_power_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *power_factor_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *forward_active_energy_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *reverse_active_energy_sensor_{nullptr};
 | 
			
		||||
  uint32_t cumulative_forward_active_energy_{0};
 | 
			
		||||
  uint32_t cumulative_reverse_active_energy_{0};
 | 
			
		||||
  uint16_t gain_metering_{7481};
 | 
			
		||||
  uint16_t gain_voltage_{26400};
 | 
			
		||||
  uint16_t gain_ct_{31251};
 | 
			
		||||
  uint16_t gain_pga_{0x4};
 | 
			
		||||
  uint16_t n_line_gain_{0x2};
 | 
			
		||||
  int line_freq_{60};
 | 
			
		||||
  float meter_constant_{3200.0f};
 | 
			
		||||
  uint32_t pl_const_{1429876};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace atm90e26
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,70 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atm90e26 {
 | 
			
		||||
 | 
			
		||||
/* Status and Special Register */
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_SOFTRESET = 0x00;  // Software Reset
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_SYSSTATUS = 0x01;  // System Status
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_FUNCEN = 0x02;     // Function Enable
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_SAGTH = 0x03;      // Voltage Sag Threshold
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_SMALLPMOD = 0x04;  // Small-Power Mode
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_LASTDATA = 0x06;   // Last Read/Write SPI/UART Value
 | 
			
		||||
 | 
			
		||||
/* Metering Calibration and Configuration Register */
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_LSB = 0x08;       // RMS/Power 16-bit LSB
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_CALSTART = 0x20;  // Calibration Start Command
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_PLCONSTH = 0x21;  // High Word of PL_Constant
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_PLCONSTL = 0x22;  // Low Word of PL_Constant
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_LGAIN = 0x23;     // L Line Calibration Gain
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_LPHI = 0x24;      // L Line Calibration Angle
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_NGAIN = 0x25;     // N Line Calibration Gain
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_NPHI = 0x26;      // N Line Calibration Angle
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_PSTARTTH = 0x27;  // Active Startup Power Threshold
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_PNOLTH = 0x28;    // Active No-Load Power Threshold
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_QSTARTTH = 0x29;  // Reactive Startup Power Threshold
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_QNOLTH = 0x2A;    // Reactive No-Load Power Threshold
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_MMODE = 0x2B;     // Metering Mode Configuration
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_CS1 = 0x2C;       // Checksum 1
 | 
			
		||||
 | 
			
		||||
/* Measurement Calibration Register */
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_ADJSTART = 0x30;  // Measurement Calibration Start Command
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_UGAIN = 0x31;     // Voltage RMS Gain
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_IGAINL = 0x32;    // L Line Current RMS Gain
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_IGAINN = 0x33;    // N Line Current RMS Gain
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_UOFFSET = 0x34;   // Voltage Offset
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_IOFFSETL = 0x35;  // L Line Current Offset
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_IOFFSETN = 0x36;  // N Line Current Offse
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_POFFSETL = 0x37;  // L Line Active Power Offset
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_QOFFSETL = 0x38;  // L Line Reactive Power Offset
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_POFFSETN = 0x39;  // N Line Active Power Offset
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_QOFFSETN = 0x3A;  // N Line Reactive Power Offset
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_CS2 = 0x3B;       // Checksum 2
 | 
			
		||||
 | 
			
		||||
/* Energy Register */
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_APENERGY = 0x40;  // Forward Active Energy
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_ANENERGY = 0x41;  // Reverse Active Energy
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_ATENERGY = 0x42;  // Absolute Active Energy
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_RPENERGY = 0x43;  // Forward (Inductive) Reactive Energy
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_RNENERG = 0x44;   // Reverse (Capacitive) Reactive Energy
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_RTENERGY = 0x45;  // Absolute Reactive Energy
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_ENSTATUS = 0x46;  // Metering Status
 | 
			
		||||
 | 
			
		||||
/* Measurement Register */
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_IRMS = 0x48;     // L Line Current RMS
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_URMS = 0x49;     // Voltage RMS
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_PMEAN = 0x4A;    // L Line Mean Active Power
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_QMEAN = 0x4B;    // L Line Mean Reactive Power
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_FREQ = 0x4C;     // Voltage Frequency
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_POWERF = 0x4D;   // L Line Power Factor
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_PANGLE = 0x4E;   // Phase Angle between Voltage and L Line Current
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_SMEAN = 0x4F;    // L Line Mean Apparent Power
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_IRMS2 = 0x68;    // N Line Current rms
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_PMEAN2 = 0x6A;   // N Line Mean Active Power
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_QMEAN2 = 0x6B;   // N Line Mean Reactive Power
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_POWERF2 = 0x6D;  // N Line Power Factor
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_PANGLE2 = 0x6E;  // Phase Angle between Voltage and N Line Current
 | 
			
		||||
static const uint8_t ATM90E26_REGISTER_SMEAN2 = 0x6F;   // N Line Mean Apparent Power
 | 
			
		||||
 | 
			
		||||
}  // namespace atm90e26
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,157 +0,0 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, spi
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_REACTIVE_POWER,
 | 
			
		||||
    CONF_VOLTAGE,
 | 
			
		||||
    CONF_CURRENT,
 | 
			
		||||
    CONF_POWER,
 | 
			
		||||
    CONF_POWER_FACTOR,
 | 
			
		||||
    CONF_FREQUENCY,
 | 
			
		||||
    CONF_FORWARD_ACTIVE_ENERGY,
 | 
			
		||||
    CONF_REVERSE_ACTIVE_ENERGY,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_ENERGY,
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_POWER_FACTOR,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ICON_LIGHTBULB,
 | 
			
		||||
    ICON_CURRENT_AC,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    STATE_CLASS_TOTAL_INCREASING,
 | 
			
		||||
    UNIT_HERTZ,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
    UNIT_VOLT_AMPS_REACTIVE,
 | 
			
		||||
    UNIT_WATT_HOURS,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_LINE_FREQUENCY = "line_frequency"
 | 
			
		||||
CONF_METER_CONSTANT = "meter_constant"
 | 
			
		||||
CONF_PL_CONST = "pl_const"
 | 
			
		||||
CONF_GAIN_PGA = "gain_pga"
 | 
			
		||||
CONF_GAIN_METERING = "gain_metering"
 | 
			
		||||
CONF_GAIN_VOLTAGE = "gain_voltage"
 | 
			
		||||
CONF_GAIN_CT = "gain_ct"
 | 
			
		||||
LINE_FREQS = {
 | 
			
		||||
    "50HZ": 50,
 | 
			
		||||
    "60HZ": 60,
 | 
			
		||||
}
 | 
			
		||||
PGA_GAINS = {
 | 
			
		||||
    "1X": 0x4,
 | 
			
		||||
    "4X": 0x0,
 | 
			
		||||
    "8X": 0x1,
 | 
			
		||||
    "16X": 0x2,
 | 
			
		||||
    "24X": 0x3,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
atm90e26_ns = cg.esphome_ns.namespace("atm90e26")
 | 
			
		||||
ATM90E26Component = atm90e26_ns.class_(
 | 
			
		||||
    "ATM90E26Component", cg.PollingComponent, spi.SPIDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(ATM90E26Component),
 | 
			
		||||
            cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_VOLT,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
                device_class=DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CURRENT): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_AMPERE,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
                device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_POWER): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_WATT,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
                device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
 | 
			
		||||
                icon=ICON_LIGHTBULB,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
                device_class=DEVICE_CLASS_POWER_FACTOR,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_WATT_HOURS,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
                device_class=DEVICE_CLASS_ENERGY,
 | 
			
		||||
                state_class=STATE_CLASS_TOTAL_INCREASING,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_WATT_HOURS,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
                device_class=DEVICE_CLASS_ENERGY,
 | 
			
		||||
                state_class=STATE_CLASS_TOTAL_INCREASING,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_HERTZ,
 | 
			
		||||
                icon=ICON_CURRENT_AC,
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
 | 
			
		||||
            cv.Required(CONF_METER_CONSTANT): cv.positive_float,
 | 
			
		||||
            cv.Optional(CONF_PL_CONST, default=1429876): cv.uint32_t,
 | 
			
		||||
            cv.Optional(CONF_GAIN_METERING, default=7481): cv.uint16_t,
 | 
			
		||||
            cv.Optional(CONF_GAIN_VOLTAGE, default=26400): cv.int_range(
 | 
			
		||||
                min=0, max=32767
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_GAIN_CT, default=31251): cv.uint16_t,
 | 
			
		||||
            cv.Optional(CONF_GAIN_PGA, default="1X"): cv.enum(PGA_GAINS, upper=True),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(spi.spi_device_schema())
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await spi.register_spi_device(var, config)
 | 
			
		||||
 | 
			
		||||
    if CONF_VOLTAGE in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_VOLTAGE])
 | 
			
		||||
        cg.add(var.set_voltage_sensor(sens))
 | 
			
		||||
    if CONF_CURRENT in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_CURRENT])
 | 
			
		||||
        cg.add(var.set_current_sensor(sens))
 | 
			
		||||
    if CONF_POWER in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_POWER])
 | 
			
		||||
        cg.add(var.set_power_sensor(sens))
 | 
			
		||||
    if CONF_REACTIVE_POWER in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_REACTIVE_POWER])
 | 
			
		||||
        cg.add(var.set_reactive_power_sensor(sens))
 | 
			
		||||
    if CONF_POWER_FACTOR in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_POWER_FACTOR])
 | 
			
		||||
        cg.add(var.set_power_factor_sensor(sens))
 | 
			
		||||
    if CONF_FORWARD_ACTIVE_ENERGY in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_FORWARD_ACTIVE_ENERGY])
 | 
			
		||||
        cg.add(var.set_forward_active_energy_sensor(sens))
 | 
			
		||||
    if CONF_REVERSE_ACTIVE_ENERGY in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_REVERSE_ACTIVE_ENERGY])
 | 
			
		||||
        cg.add(var.set_reverse_active_energy_sensor(sens))
 | 
			
		||||
    if CONF_FREQUENCY in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_FREQUENCY])
 | 
			
		||||
        cg.add(var.set_freq_sensor(sens))
 | 
			
		||||
    cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
 | 
			
		||||
    cg.add(var.set_meter_constant(config[CONF_METER_CONSTANT]))
 | 
			
		||||
    cg.add(var.set_pl_const(config[CONF_PL_CONST]))
 | 
			
		||||
    cg.add(var.set_gain_metering(config[CONF_GAIN_METERING]))
 | 
			
		||||
    cg.add(var.set_gain_voltage(config[CONF_GAIN_VOLTAGE]))
 | 
			
		||||
    cg.add(var.set_gain_ct(config[CONF_GAIN_CT]))
 | 
			
		||||
    cg.add(var.set_gain_pga(config[CONF_GAIN_PGA]))
 | 
			
		||||
@@ -442,7 +442,7 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) {
 | 
			
		||||
void BedJetHub::send_local_time() {
 | 
			
		||||
  if (this->time_id_.has_value()) {
 | 
			
		||||
    auto *time_id = *this->time_id_;
 | 
			
		||||
    ESPTime now = time_id->now();
 | 
			
		||||
    time::ESPTime now = time_id->now();
 | 
			
		||||
    if (now.is_valid()) {
 | 
			
		||||
      this->set_clock(now.hour, now.minute);
 | 
			
		||||
      ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute);
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,6 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TIME
 | 
			
		||||
#include "esphome/components/time/real_time_clock.h"
 | 
			
		||||
#include "esphome/core/time.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
 
 | 
			
		||||
@@ -95,14 +95,6 @@ DEVICE_CLASSES = [
 | 
			
		||||
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
CONF_TIME_OFF = "time_off"
 | 
			
		||||
CONF_TIME_ON = "time_on"
 | 
			
		||||
 | 
			
		||||
DEFAULT_DELAY = "1s"
 | 
			
		||||
DEFAULT_TIME_OFF = "100ms"
 | 
			
		||||
DEFAULT_TIME_ON = "900ms"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor")
 | 
			
		||||
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase)
 | 
			
		||||
BinarySensorInitiallyOff = binary_sensor_ns.class_(
 | 
			
		||||
@@ -146,75 +138,47 @@ FILTER_REGISTRY = Registry()
 | 
			
		||||
validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def register_filter(name, filter_type, schema):
 | 
			
		||||
    return FILTER_REGISTRY.register(name, filter_type, schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_filter("invert", InvertFilter, {})
 | 
			
		||||
@FILTER_REGISTRY.register("invert", InvertFilter, {})
 | 
			
		||||
async def invert_filter_to_code(config, filter_id):
 | 
			
		||||
    return cg.new_Pvariable(filter_id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_filter(
 | 
			
		||||
    "delayed_on_off",
 | 
			
		||||
    DelayedOnOffFilter,
 | 
			
		||||
    cv.Any(
 | 
			
		||||
        cv.templatable(cv.positive_time_period_milliseconds),
 | 
			
		||||
        cv.Schema(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Required(CONF_TIME_ON): cv.templatable(
 | 
			
		||||
                    cv.positive_time_period_milliseconds
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Required(CONF_TIME_OFF): cv.templatable(
 | 
			
		||||
                    cv.positive_time_period_milliseconds
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        msg="'delayed_on_off' filter requires either a delay time to be used for both "
 | 
			
		||||
        "turn-on and turn-off delays, or two parameters 'time_on' and 'time_off' if "
 | 
			
		||||
        "different delay times are required.",
 | 
			
		||||
    ),
 | 
			
		||||
@FILTER_REGISTRY.register(
 | 
			
		||||
    "delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds
 | 
			
		||||
)
 | 
			
		||||
async def delayed_on_off_filter_to_code(config, filter_id):
 | 
			
		||||
    var = cg.new_Pvariable(filter_id)
 | 
			
		||||
    var = cg.new_Pvariable(filter_id, config)
 | 
			
		||||
    await cg.register_component(var, {})
 | 
			
		||||
    if isinstance(config, dict):
 | 
			
		||||
        template_ = await cg.templatable(config[CONF_TIME_ON], [], cg.uint32)
 | 
			
		||||
        cg.add(var.set_on_delay(template_))
 | 
			
		||||
        template_ = await cg.templatable(config[CONF_TIME_OFF], [], cg.uint32)
 | 
			
		||||
        cg.add(var.set_off_delay(template_))
 | 
			
		||||
    else:
 | 
			
		||||
        template_ = await cg.templatable(config, [], cg.uint32)
 | 
			
		||||
        cg.add(var.set_on_delay(template_))
 | 
			
		||||
        cg.add(var.set_off_delay(template_))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_filter(
 | 
			
		||||
    "delayed_on", DelayedOnFilter, cv.templatable(cv.positive_time_period_milliseconds)
 | 
			
		||||
@FILTER_REGISTRY.register(
 | 
			
		||||
    "delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds
 | 
			
		||||
)
 | 
			
		||||
async def delayed_on_filter_to_code(config, filter_id):
 | 
			
		||||
    var = cg.new_Pvariable(filter_id)
 | 
			
		||||
    var = cg.new_Pvariable(filter_id, config)
 | 
			
		||||
    await cg.register_component(var, {})
 | 
			
		||||
    template_ = await cg.templatable(config, [], cg.uint32)
 | 
			
		||||
    cg.add(var.set_delay(template_))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_filter(
 | 
			
		||||
    "delayed_off",
 | 
			
		||||
    DelayedOffFilter,
 | 
			
		||||
    cv.templatable(cv.positive_time_period_milliseconds),
 | 
			
		||||
@FILTER_REGISTRY.register(
 | 
			
		||||
    "delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds
 | 
			
		||||
)
 | 
			
		||||
async def delayed_off_filter_to_code(config, filter_id):
 | 
			
		||||
    var = cg.new_Pvariable(filter_id)
 | 
			
		||||
    var = cg.new_Pvariable(filter_id, config)
 | 
			
		||||
    await cg.register_component(var, {})
 | 
			
		||||
    template_ = await cg.templatable(config, [], cg.uint32)
 | 
			
		||||
    cg.add(var.set_delay(template_))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_filter(
 | 
			
		||||
CONF_TIME_OFF = "time_off"
 | 
			
		||||
CONF_TIME_ON = "time_on"
 | 
			
		||||
 | 
			
		||||
DEFAULT_DELAY = "1s"
 | 
			
		||||
DEFAULT_TIME_OFF = "100ms"
 | 
			
		||||
DEFAULT_TIME_ON = "900ms"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@FILTER_REGISTRY.register(
 | 
			
		||||
    "autorepeat",
 | 
			
		||||
    AutorepeatFilter,
 | 
			
		||||
    cv.All(
 | 
			
		||||
@@ -251,7 +215,7 @@ async def autorepeat_filter_to_code(config, filter_id):
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_filter("lambda", LambdaFilter, cv.returning_lambda)
 | 
			
		||||
@FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda)
 | 
			
		||||
async def lambda_filter_to_code(config, filter_id):
 | 
			
		||||
    lambda_ = await cg.process_lambda(
 | 
			
		||||
        config, [(bool, "x")], return_type=cg.optional.template(bool)
 | 
			
		||||
@@ -359,18 +323,6 @@ def validate_multi_click_timing(value):
 | 
			
		||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_click_timing(value):
 | 
			
		||||
    for v in value:
 | 
			
		||||
        min_length = v.get(CONF_MIN_LENGTH)
 | 
			
		||||
        max_length = v.get(CONF_MAX_LENGTH)
 | 
			
		||||
        if max_length < min_length:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"Max length ({max_length}) must be larger than min length ({min_length})."
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(BinarySensor),
 | 
			
		||||
@@ -390,33 +342,27 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_CLICK): cv.All(
 | 
			
		||||
            automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_MIN_LENGTH, default="50ms"
 | 
			
		||||
                    ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_MAX_LENGTH, default="350ms"
 | 
			
		||||
                    ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            validate_click_timing,
 | 
			
		||||
        cv.Optional(CONF_ON_CLICK): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_MIN_LENGTH, default="50ms"
 | 
			
		||||
                ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_MAX_LENGTH, default="350ms"
 | 
			
		||||
                ): cv.positive_time_period_milliseconds,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
 | 
			
		||||
            automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_MIN_LENGTH, default="50ms"
 | 
			
		||||
                    ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_MAX_LENGTH, default="350ms"
 | 
			
		||||
                    ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            validate_click_timing,
 | 
			
		||||
        cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_MIN_LENGTH, default="50ms"
 | 
			
		||||
                ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_MAX_LENGTH, default="350ms"
 | 
			
		||||
                ): cv.positive_time_period_milliseconds,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -26,20 +26,22 @@ void Filter::input(bool value, bool is_initial) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DelayedOnOffFilter::DelayedOnOffFilter(uint32_t delay) : delay_(delay) {}
 | 
			
		||||
optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
 | 
			
		||||
  if (value) {
 | 
			
		||||
    this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
 | 
			
		||||
    this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(true, is_initial); });
 | 
			
		||||
  } else {
 | 
			
		||||
    this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
 | 
			
		||||
    this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); });
 | 
			
		||||
  }
 | 
			
		||||
  return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
 | 
			
		||||
 | 
			
		||||
DelayedOnFilter::DelayedOnFilter(uint32_t delay) : delay_(delay) {}
 | 
			
		||||
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
 | 
			
		||||
  if (value) {
 | 
			
		||||
    this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
 | 
			
		||||
    this->set_timeout("ON", this->delay_, [this, is_initial]() { this->output(true, is_initial); });
 | 
			
		||||
    return {};
 | 
			
		||||
  } else {
 | 
			
		||||
    this->cancel_timeout("ON");
 | 
			
		||||
@@ -49,9 +51,10 @@ optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
 | 
			
		||||
 | 
			
		||||
float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
 | 
			
		||||
 | 
			
		||||
DelayedOffFilter::DelayedOffFilter(uint32_t delay) : delay_(delay) {}
 | 
			
		||||
optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
 | 
			
		||||
  if (!value) {
 | 
			
		||||
    this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
 | 
			
		||||
    this->set_timeout("OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); });
 | 
			
		||||
    return {};
 | 
			
		||||
  } else {
 | 
			
		||||
    this->cancel_timeout("OFF");
 | 
			
		||||
@@ -111,6 +114,15 @@ LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move
 | 
			
		||||
 | 
			
		||||
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
 | 
			
		||||
 | 
			
		||||
optional<bool> UniqueFilter::new_value(bool value, bool is_initial) {
 | 
			
		||||
  if (this->last_value_.has_value() && *this->last_value_ == value) {
 | 
			
		||||
    return {};
 | 
			
		||||
  } else {
 | 
			
		||||
    this->last_value_ = value;
 | 
			
		||||
    return value;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace binary_sensor
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
@@ -30,40 +29,38 @@ class Filter {
 | 
			
		||||
 | 
			
		||||
class DelayedOnOffFilter : public Filter, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DelayedOnOffFilter(uint32_t delay);
 | 
			
		||||
 | 
			
		||||
  optional<bool> new_value(bool value, bool is_initial) override;
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  template<typename T> void set_on_delay(T delay) { this->on_delay_ = delay; }
 | 
			
		||||
  template<typename T> void set_off_delay(T delay) { this->off_delay_ = delay; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  TemplatableValue<uint32_t> on_delay_{};
 | 
			
		||||
  TemplatableValue<uint32_t> off_delay_{};
 | 
			
		||||
  uint32_t delay_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DelayedOnFilter : public Filter, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DelayedOnFilter(uint32_t delay);
 | 
			
		||||
 | 
			
		||||
  optional<bool> new_value(bool value, bool is_initial) override;
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  template<typename T> void set_delay(T delay) { this->delay_ = delay; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  TemplatableValue<uint32_t> delay_{};
 | 
			
		||||
  uint32_t delay_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DelayedOffFilter : public Filter, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DelayedOffFilter(uint32_t delay);
 | 
			
		||||
 | 
			
		||||
  optional<bool> new_value(bool value, bool is_initial) override;
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  template<typename T> void set_delay(T delay) { this->delay_ = delay; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  TemplatableValue<uint32_t> delay_{};
 | 
			
		||||
  uint32_t delay_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class InvertFilter : public Filter {
 | 
			
		||||
@@ -108,6 +105,14 @@ class LambdaFilter : public Filter {
 | 
			
		||||
  std::function<optional<bool>(bool)> f_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class UniqueFilter : public Filter {
 | 
			
		||||
 public:
 | 
			
		||||
  optional<bool> new_value(bool value, bool is_initial) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  optional<bool> last_value_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace binary_sensor
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_IBEACON_MAJOR,
 | 
			
		||||
    CONF_IBEACON_MINOR,
 | 
			
		||||
    CONF_IBEACON_UUID,
 | 
			
		||||
    CONF_MIN_RSSI,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32_ble_tracker"]
 | 
			
		||||
@@ -38,9 +37,6 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t,
 | 
			
		||||
            cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t,
 | 
			
		||||
            cv.Optional(CONF_IBEACON_UUID): cv.uuid,
 | 
			
		||||
            cv.Optional(CONF_MIN_RSSI): cv.All(
 | 
			
		||||
                cv.decibel, cv.int_range(min=-100, max=-30)
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
			
		||||
@@ -55,9 +51,6 @@ async def to_code(config):
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await esp32_ble_tracker.register_ble_device(var, config)
 | 
			
		||||
 | 
			
		||||
    if CONF_MIN_RSSI in config:
 | 
			
		||||
        cg.add(var.set_minimum_rssi(config[CONF_MIN_RSSI]))
 | 
			
		||||
 | 
			
		||||
    if CONF_MAC_ADDRESS in config:
 | 
			
		||||
        cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -41,19 +41,12 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
 | 
			
		||||
    this->check_ibeacon_minor_ = true;
 | 
			
		||||
    this->ibeacon_minor_ = minor;
 | 
			
		||||
  }
 | 
			
		||||
  void set_minimum_rssi(int rssi) {
 | 
			
		||||
    this->check_minimum_rssi_ = true;
 | 
			
		||||
    this->minimum_rssi_ = rssi;
 | 
			
		||||
  }
 | 
			
		||||
  void on_scan_end() override {
 | 
			
		||||
    if (!this->found_)
 | 
			
		||||
      this->publish_state(false);
 | 
			
		||||
    this->found_ = false;
 | 
			
		||||
  }
 | 
			
		||||
  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
 | 
			
		||||
    if (this->check_minimum_rssi_ && this->minimum_rssi_ > device.get_rssi()) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    switch (this->match_by_) {
 | 
			
		||||
      case MATCH_BY_MAC_ADDRESS:
 | 
			
		||||
        if (device.address_uint64() == this->address_) {
 | 
			
		||||
@@ -103,21 +96,17 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
 | 
			
		||||
  enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID };
 | 
			
		||||
  MatchType match_by_;
 | 
			
		||||
 | 
			
		||||
  bool found_{false};
 | 
			
		||||
 | 
			
		||||
  uint64_t address_;
 | 
			
		||||
 | 
			
		||||
  esp32_ble_tracker::ESPBTUUID uuid_;
 | 
			
		||||
 | 
			
		||||
  esp32_ble_tracker::ESPBTUUID ibeacon_uuid_;
 | 
			
		||||
  uint16_t ibeacon_major_{0};
 | 
			
		||||
  uint16_t ibeacon_minor_{0};
 | 
			
		||||
 | 
			
		||||
  int minimum_rssi_{0};
 | 
			
		||||
 | 
			
		||||
  bool check_ibeacon_major_{false};
 | 
			
		||||
  bool check_ibeacon_minor_{false};
 | 
			
		||||
  bool check_minimum_rssi_{false};
 | 
			
		||||
 | 
			
		||||
  bool found_{false};
 | 
			
		||||
  uint16_t ibeacon_major_;
 | 
			
		||||
  bool check_ibeacon_major_;
 | 
			
		||||
  uint16_t ibeacon_minor_;
 | 
			
		||||
  bool check_ibeacon_minor_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ble_presence
 | 
			
		||||
 
 | 
			
		||||
@@ -102,9 +102,8 @@ class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDevi
 | 
			
		||||
 | 
			
		||||
  esp32_ble_tracker::ESPBTUUID ibeacon_uuid_;
 | 
			
		||||
  uint16_t ibeacon_major_;
 | 
			
		||||
  uint16_t ibeacon_minor_;
 | 
			
		||||
 | 
			
		||||
  bool check_ibeacon_major_;
 | 
			
		||||
  uint16_t ibeacon_minor_;
 | 
			
		||||
  bool check_ibeacon_minor_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
#include "bluetooth_connection.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/api/api_pb2.h"
 | 
			
		||||
#include "esphome/components/api/api_server.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
@@ -20,21 +20,24 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
      this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->disconnect.reason);
 | 
			
		||||
      this->set_address(0);
 | 
			
		||||
      this->proxy_->send_connections_free();
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->proxy_->get_bluetooth_connections_limit());
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      if (param->open.conn_id != this->conn_id_)
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
 | 
			
		||||
        this->proxy_->send_device_connection(this->address_, false, 0, param->open.status);
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->open.status);
 | 
			
		||||
        this->set_address(0);
 | 
			
		||||
        this->proxy_->send_connections_free();
 | 
			
		||||
        api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
			
		||||
                                                                this->proxy_->get_bluetooth_connections_limit());
 | 
			
		||||
      } else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
 | 
			
		||||
        this->proxy_->send_device_connection(this->address_, true, this->mtu_);
 | 
			
		||||
        this->proxy_->send_connections_free();
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
 | 
			
		||||
        api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
			
		||||
                                                                this->proxy_->get_bluetooth_connections_limit());
 | 
			
		||||
      }
 | 
			
		||||
      this->seen_mtu_or_services_ = false;
 | 
			
		||||
      break;
 | 
			
		||||
@@ -49,8 +52,9 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
        this->seen_mtu_or_services_ = true;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      this->proxy_->send_device_connection(this->address_, true, this->mtu_);
 | 
			
		||||
      this->proxy_->send_connections_free();
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->proxy_->get_bluetooth_connections_limit());
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
@@ -63,8 +67,9 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
        this->seen_mtu_or_services_ = true;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      this->proxy_->send_device_connection(this->address_, true, this->mtu_);
 | 
			
		||||
      this->proxy_->send_connections_free();
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->proxy_->get_bluetooth_connections_limit());
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_READ_DESCR_EVT:
 | 
			
		||||
@@ -74,7 +79,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
      if (param->read.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->read.handle, param->read.status);
 | 
			
		||||
        this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status);
 | 
			
		||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->read.handle, param->read.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTReadResponse resp;
 | 
			
		||||
@@ -84,7 +89,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
      for (uint16_t i = 0; i < param->read.value_len; i++) {
 | 
			
		||||
        resp.data.push_back(param->read.value[i]);
 | 
			
		||||
      }
 | 
			
		||||
      this->proxy_->get_api_connection()->send_bluetooth_gatt_read_response(resp);
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_read_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_WRITE_CHAR_EVT:
 | 
			
		||||
@@ -94,13 +99,13 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
      if (param->write.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->write.handle, param->write.status);
 | 
			
		||||
        this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status);
 | 
			
		||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->write.handle, param->write.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTWriteResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->write.handle;
 | 
			
		||||
      this->proxy_->get_api_connection()->send_bluetooth_gatt_write_response(resp);
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_write_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
 | 
			
		||||
@@ -108,26 +113,28 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d",
 | 
			
		||||
                 this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle,
 | 
			
		||||
                 param->unreg_for_notify.status);
 | 
			
		||||
        this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status);
 | 
			
		||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->unreg_for_notify.handle,
 | 
			
		||||
                                                          param->unreg_for_notify.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTNotifyResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->unreg_for_notify.handle;
 | 
			
		||||
      this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_response(resp);
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_notify_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      if (param->reg_for_notify.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status);
 | 
			
		||||
        this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status);
 | 
			
		||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->reg_for_notify.handle,
 | 
			
		||||
                                                          param->reg_for_notify.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTNotifyResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->reg_for_notify.handle;
 | 
			
		||||
      this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_response(resp);
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_notify_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_NOTIFY_EVT: {
 | 
			
		||||
@@ -142,7 +149,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
      for (uint16_t i = 0; i < param->notify.value_len; i++) {
 | 
			
		||||
        resp.data.push_back(param->notify.value[i]);
 | 
			
		||||
      }
 | 
			
		||||
      this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_data_response(resp);
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_notify_data_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
@@ -159,9 +166,10 @@ void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl
 | 
			
		||||
      if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->ble_security.auth_cmpl.success) {
 | 
			
		||||
        this->proxy_->send_device_pairing(this->address_, true);
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_pairing(this->address_, true);
 | 
			
		||||
      } else {
 | 
			
		||||
        this->proxy_->send_device_pairing(this->address_, false, param->ble_security.auth_cmpl.fail_reason);
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_pairing(this->address_, false,
 | 
			
		||||
                                                              param->ble_security.auth_cmpl.fail_reason);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
@@ -275,10 +283,6 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl
 | 
			
		||||
  return ESP_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {
 | 
			
		||||
  return this->proxy_->get_advertisement_parser_type();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace bluetooth_proxy
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
 | 
			
		||||
  bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
 | 
			
		||||
  esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
 | 
			
		||||
 | 
			
		||||
  esp_err_t read_characteristic(uint16_t handle);
 | 
			
		||||
  esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
#include "bluetooth_proxy.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/macros.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/api/api_server.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bluetooth_proxy {
 | 
			
		||||
 | 
			
		||||
@@ -26,39 +27,15 @@ std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
 | 
			
		||||
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
 | 
			
		||||
 | 
			
		||||
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
			
		||||
  if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_)
 | 
			
		||||
  if (!api::global_api_server->is_connected())
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(),
 | 
			
		||||
           device.get_rssi());
 | 
			
		||||
  this->send_api_packet_(device);
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) {
 | 
			
		||||
  if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  api::BluetoothLERawAdvertisementsResponse resp;
 | 
			
		||||
  for (size_t i = 0; i < count; i++) {
 | 
			
		||||
    auto &result = advertisements[i];
 | 
			
		||||
    api::BluetoothLERawAdvertisement adv;
 | 
			
		||||
    adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
 | 
			
		||||
    adv.rssi = result.rssi;
 | 
			
		||||
    adv.address_type = result.ble_addr_type;
 | 
			
		||||
 | 
			
		||||
    uint8_t length = result.adv_data_len + result.scan_rsp_len;
 | 
			
		||||
    adv.data.reserve(length);
 | 
			
		||||
    for (uint16_t i = 0; i < length; i++) {
 | 
			
		||||
      adv.data.push_back(result.ble_adv[i]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    resp.advertisements.push_back(std::move(adv));
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "Proxying %d packets", count);
 | 
			
		||||
  this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
			
		||||
  api::BluetoothLEAdvertisementResponse resp;
 | 
			
		||||
  resp.address = device.address_uint64();
 | 
			
		||||
@@ -81,7 +58,7 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
 | 
			
		||||
    manufacturer_data.data.assign(data.data.begin(), data.data.end());
 | 
			
		||||
    resp.manufacturer_data.push_back(std::move(manufacturer_data));
 | 
			
		||||
  }
 | 
			
		||||
  this->api_connection_->send_bluetooth_le_advertisement(resp);
 | 
			
		||||
  api::global_api_server->send_bluetooth_le_advertisement(resp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::dump_config() {
 | 
			
		||||
@@ -104,7 +81,7 @@ int BluetoothProxy::get_bluetooth_connections_free() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::loop() {
 | 
			
		||||
  if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
 | 
			
		||||
  if (!api::global_api_server->is_connected()) {
 | 
			
		||||
    for (auto *connection : this->connections_) {
 | 
			
		||||
      if (connection->get_address() != 0) {
 | 
			
		||||
        connection->disconnect();
 | 
			
		||||
@@ -115,7 +92,7 @@ void BluetoothProxy::loop() {
 | 
			
		||||
  for (auto *connection : this->connections_) {
 | 
			
		||||
    if (connection->send_service_ == connection->service_count_) {
 | 
			
		||||
      connection->send_service_ = DONE_SENDING_SERVICES;
 | 
			
		||||
      this->send_gatt_services_done(connection->get_address());
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_services_done(connection->get_address());
 | 
			
		||||
      if (connection->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
 | 
			
		||||
          connection->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
 | 
			
		||||
        connection->release_services();
 | 
			
		||||
@@ -193,17 +170,11 @@ void BluetoothProxy::loop() {
 | 
			
		||||
        service_resp.characteristics.push_back(std::move(characteristic_resp));
 | 
			
		||||
      }
 | 
			
		||||
      resp.services.push_back(std::move(service_resp));
 | 
			
		||||
      this->api_connection_->send_bluetooth_gatt_get_services_response(resp);
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_services(resp);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() {
 | 
			
		||||
  if (this->raw_advertisements_)
 | 
			
		||||
    return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS;
 | 
			
		||||
  return esp32_ble_tracker::AdvertisementParserType::PARSED_ADVERTISEMENTS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
 | 
			
		||||
  for (auto *connection : this->connections_) {
 | 
			
		||||
    if (connection->get_address() == address)
 | 
			
		||||
@@ -237,15 +208,16 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
			
		||||
      auto *connection = this->get_connection_(msg.address, true);
 | 
			
		||||
      if (connection == nullptr) {
 | 
			
		||||
        ESP_LOGW(TAG, "No free connections available");
 | 
			
		||||
        this->send_device_connection(msg.address, false);
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(msg.address, false);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (connection->state() == espbt::ClientState::CONNECTED ||
 | 
			
		||||
          connection->state() == espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Connection already established", connection->get_connection_index(),
 | 
			
		||||
                 connection->address_str().c_str());
 | 
			
		||||
        this->send_device_connection(msg.address, true);
 | 
			
		||||
        this->send_connections_free();
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(msg.address, true);
 | 
			
		||||
        api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                                this->get_bluetooth_connections_limit());
 | 
			
		||||
        return;
 | 
			
		||||
      } else if (connection->state() == espbt::ClientState::SEARCHING) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already searching for device",
 | 
			
		||||
@@ -291,22 +263,25 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
			
		||||
      } else {
 | 
			
		||||
        connection->set_state(espbt::ClientState::SEARCHING);
 | 
			
		||||
      }
 | 
			
		||||
      this->send_connections_free();
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->get_bluetooth_connections_limit());
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
 | 
			
		||||
      auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
      if (connection == nullptr) {
 | 
			
		||||
        this->send_device_connection(msg.address, false);
 | 
			
		||||
        this->send_connections_free();
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(msg.address, false);
 | 
			
		||||
        api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                                this->get_bluetooth_connections_limit());
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (connection->state() != espbt::ClientState::IDLE) {
 | 
			
		||||
        connection->disconnect();
 | 
			
		||||
      } else {
 | 
			
		||||
        connection->set_address(0);
 | 
			
		||||
        this->send_device_connection(msg.address, false);
 | 
			
		||||
        this->send_connections_free();
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(msg.address, false);
 | 
			
		||||
        api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                                this->get_bluetooth_connections_limit());
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -316,10 +291,10 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
			
		||||
        if (!connection->is_paired()) {
 | 
			
		||||
          auto err = connection->pair();
 | 
			
		||||
          if (err != ESP_OK) {
 | 
			
		||||
            this->send_device_pairing(msg.address, false, err);
 | 
			
		||||
            api::global_api_server->send_bluetooth_device_pairing(msg.address, false, err);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          this->send_device_pairing(msg.address, true);
 | 
			
		||||
          api::global_api_server->send_bluetooth_device_pairing(msg.address, true);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
@@ -328,20 +303,14 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
			
		||||
      esp_bd_addr_t address;
 | 
			
		||||
      uint64_to_bd_addr(msg.address, address);
 | 
			
		||||
      esp_err_t ret = esp_ble_remove_bond_device(address);
 | 
			
		||||
      this->send_device_pairing(msg.address, ret == ESP_OK, ret);
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_unpairing(msg.address, ret == ESP_OK, ret);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: {
 | 
			
		||||
      esp_bd_addr_t address;
 | 
			
		||||
      uint64_to_bd_addr(msg.address, address);
 | 
			
		||||
      esp_err_t ret = esp_ble_gattc_cache_clean(address);
 | 
			
		||||
      api::BluetoothDeviceClearCacheResponse call;
 | 
			
		||||
      call.address = msg.address;
 | 
			
		||||
      call.success = ret == ESP_OK;
 | 
			
		||||
      call.error = ret;
 | 
			
		||||
 | 
			
		||||
      this->api_connection_->send_bluetooth_device_clear_cache_response(call);
 | 
			
		||||
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_clear_cache(msg.address, ret == ESP_OK, ret);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -351,13 +320,13 @@ void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &ms
 | 
			
		||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
  if (connection == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected");
 | 
			
		||||
    this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto err = connection->read_characteristic(msg.handle);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    this->send_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -365,13 +334,13 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &
 | 
			
		||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
  if (connection == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected");
 | 
			
		||||
    this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto err = connection->write_characteristic(msg.handle, msg.data, msg.response);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    this->send_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -379,13 +348,13 @@ void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTRead
 | 
			
		||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
  if (connection == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot read GATT descriptor, not connected");
 | 
			
		||||
    this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto err = connection->read_descriptor(msg.handle);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    this->send_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -393,13 +362,13 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri
 | 
			
		||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
  if (connection == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot write GATT descriptor, not connected");
 | 
			
		||||
    this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto err = connection->write_descriptor(msg.handle, msg.data, true);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    this->send_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -407,12 +376,12 @@ void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetSer
 | 
			
		||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
  if (connection == nullptr || !connection->connected()) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot get GATT services, not connected");
 | 
			
		||||
    this->send_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!connection->service_count_) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str());
 | 
			
		||||
    this->send_gatt_services_done(msg.address);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_services_done(msg.address);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (connection->send_service_ ==
 | 
			
		||||
@@ -424,91 +393,16 @@ void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest
 | 
			
		||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
  if (connection == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot notify GATT characteristic, not connected");
 | 
			
		||||
    this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto err = connection->notify_characteristic(msg.handle, msg.enable);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    this->send_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags) {
 | 
			
		||||
  if (this->api_connection_ != nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->api_connection_ = api_connection;
 | 
			
		||||
  this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
 | 
			
		||||
  this->parent_->recalculate_advertisement_parser_types();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) {
 | 
			
		||||
  if (this->api_connection_ != api_connection) {
 | 
			
		||||
    ESP_LOGV(TAG, "API connection is not subscribed");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->api_connection_ = nullptr;
 | 
			
		||||
  this->raw_advertisements_ = false;
 | 
			
		||||
  this->parent_->recalculate_advertisement_parser_types();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
 | 
			
		||||
  if (this->api_connection_ == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
  api::BluetoothDeviceConnectionResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.connected = connected;
 | 
			
		||||
  call.mtu = mtu;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
  this->api_connection_->send_bluetooth_device_connection_response(call);
 | 
			
		||||
}
 | 
			
		||||
void BluetoothProxy::send_connections_free() {
 | 
			
		||||
  if (this->api_connection_ == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
  api::BluetoothConnectionsFreeResponse call;
 | 
			
		||||
  call.free = this->get_bluetooth_connections_free();
 | 
			
		||||
  call.limit = this->get_bluetooth_connections_limit();
 | 
			
		||||
  this->api_connection_->send_bluetooth_connections_free_response(call);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::send_gatt_services_done(uint64_t address) {
 | 
			
		||||
  if (this->api_connection_ == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
  api::BluetoothGATTGetServicesDoneResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  this->api_connection_->send_bluetooth_gatt_get_services_done_response(call);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
 | 
			
		||||
  if (this->api_connection_ == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
  api::BluetoothGATTErrorResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.handle = handle;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
  this->api_connection_->send_bluetooth_gatt_error_response(call);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::send_device_pairing(uint64_t address, bool paired, esp_err_t error) {
 | 
			
		||||
  api::BluetoothDevicePairingResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.paired = paired;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
 | 
			
		||||
  this->api_connection_->send_bluetooth_device_pairing_response(call);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_err_t error) {
 | 
			
		||||
  api::BluetoothDeviceUnpairingResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.success = success;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
 | 
			
		||||
  this->api_connection_->send_bluetooth_device_unpairing_response(call);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BluetoothProxy *global_bluetooth_proxy = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
}  // namespace bluetooth_proxy
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/api/api_connection.h"
 | 
			
		||||
#include "esphome/components/api/api_pb2.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
@@ -22,36 +21,12 @@ static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
 | 
			
		||||
 | 
			
		||||
using namespace esp32_ble_client;
 | 
			
		||||
 | 
			
		||||
// Legacy versions:
 | 
			
		||||
// Version 1: Initial version without active connections
 | 
			
		||||
// Version 2: Support for active connections
 | 
			
		||||
// Version 3: New connection API
 | 
			
		||||
// Version 4: Pairing support
 | 
			
		||||
// Version 5: Cache clear support
 | 
			
		||||
static const uint32_t LEGACY_ACTIVE_CONNECTIONS_VERSION = 5;
 | 
			
		||||
static const uint32_t LEGACY_PASSIVE_ONLY_VERSION = 1;
 | 
			
		||||
 | 
			
		||||
enum BluetoothProxyFeature : uint32_t {
 | 
			
		||||
  FEATURE_PASSIVE_SCAN = 1 << 0,
 | 
			
		||||
  FEATURE_ACTIVE_CONNECTIONS = 1 << 1,
 | 
			
		||||
  FEATURE_REMOTE_CACHING = 1 << 2,
 | 
			
		||||
  FEATURE_PAIRING = 1 << 3,
 | 
			
		||||
  FEATURE_CACHE_CLEARING = 1 << 4,
 | 
			
		||||
  FEATURE_RAW_ADVERTISEMENTS = 1 << 5,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum BluetoothProxySubscriptionFlag : uint32_t {
 | 
			
		||||
  SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  BluetoothProxy();
 | 
			
		||||
  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
 | 
			
		||||
  bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
 | 
			
		||||
 | 
			
		||||
  void register_connection(BluetoothConnection *connection) {
 | 
			
		||||
    this->connections_.push_back(connection);
 | 
			
		||||
@@ -69,18 +44,6 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
			
		||||
  int get_bluetooth_connections_free();
 | 
			
		||||
  int get_bluetooth_connections_limit() { return this->connections_.size(); }
 | 
			
		||||
 | 
			
		||||
  void subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags);
 | 
			
		||||
  void unsubscribe_api_connection(api::APIConnection *api_connection);
 | 
			
		||||
  api::APIConnection *get_api_connection() { return this->api_connection_; }
 | 
			
		||||
 | 
			
		||||
  void send_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_connections_free();
 | 
			
		||||
  void send_gatt_services_done(uint64_t address);
 | 
			
		||||
  void send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error);
 | 
			
		||||
  void send_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK);
 | 
			
		||||
 | 
			
		||||
  static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) {
 | 
			
		||||
    bd_addr[0] = (address >> 40) & 0xff;
 | 
			
		||||
    bd_addr[1] = (address >> 32) & 0xff;
 | 
			
		||||
@@ -93,27 +56,6 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
			
		||||
  void set_active(bool active) { this->active_ = active; }
 | 
			
		||||
  bool has_active() { return this->active_; }
 | 
			
		||||
 | 
			
		||||
  uint32_t get_legacy_version() const {
 | 
			
		||||
    if (this->active_) {
 | 
			
		||||
      return LEGACY_ACTIVE_CONNECTIONS_VERSION;
 | 
			
		||||
    }
 | 
			
		||||
    return LEGACY_PASSIVE_ONLY_VERSION;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint32_t get_feature_flags() const {
 | 
			
		||||
    uint32_t flags = 0;
 | 
			
		||||
    flags |= BluetoothProxyFeature::FEATURE_PASSIVE_SCAN;
 | 
			
		||||
    flags |= BluetoothProxyFeature::FEATURE_RAW_ADVERTISEMENTS;
 | 
			
		||||
    if (this->active_) {
 | 
			
		||||
      flags |= BluetoothProxyFeature::FEATURE_ACTIVE_CONNECTIONS;
 | 
			
		||||
      flags |= BluetoothProxyFeature::FEATURE_REMOTE_CACHING;
 | 
			
		||||
      flags |= BluetoothProxyFeature::FEATURE_PAIRING;
 | 
			
		||||
      flags |= BluetoothProxyFeature::FEATURE_CACHE_CLEARING;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return flags;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
 | 
			
		||||
 | 
			
		||||
@@ -122,12 +64,18 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
			
		||||
  bool active_;
 | 
			
		||||
 | 
			
		||||
  std::vector<BluetoothConnection *> connections_{};
 | 
			
		||||
  api::APIConnection *api_connection_{nullptr};
 | 
			
		||||
  bool raw_advertisements_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern BluetoothProxy *global_bluetooth_proxy;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
// Version 1: Initial version without active connections
 | 
			
		||||
// Version 2: Support for active connections
 | 
			
		||||
// Version 3: New connection API
 | 
			
		||||
// Version 4: Pairing support
 | 
			
		||||
// Version 5: Cache clear support
 | 
			
		||||
static const uint32_t ACTIVE_CONNECTIONS_VERSION = 5;
 | 
			
		||||
static const uint32_t PASSIVE_ONLY_VERSION = 1;
 | 
			
		||||
 | 
			
		||||
}  // namespace bluetooth_proxy
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,8 @@ from esphome.const import (
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_PRESSURE,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
    DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
 | 
			
		||||
    DEVICE_CLASS_PRESSURE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
@@ -19,6 +17,8 @@ from esphome.const import (
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
    ICON_GAS_CYLINDER,
 | 
			
		||||
    ICON_GAUGE,
 | 
			
		||||
    ICON_THERMOMETER,
 | 
			
		||||
    ICON_WATER_PERCENT,
 | 
			
		||||
)
 | 
			
		||||
from . import (
 | 
			
		||||
    BME680BSECComponent,
 | 
			
		||||
@@ -35,6 +35,7 @@ CONF_CO2_EQUIVALENT = "co2_equivalent"
 | 
			
		||||
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
 | 
			
		||||
UNIT_IAQ = "IAQ"
 | 
			
		||||
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
 | 
			
		||||
ICON_TEST_TUBE = "mdi:test-tube"
 | 
			
		||||
 | 
			
		||||
TYPES = [
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
@@ -52,6 +53,7 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
        cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
 | 
			
		||||
        cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_CELSIUS,
 | 
			
		||||
            icon=ICON_THERMOMETER,
 | 
			
		||||
            accuracy_decimals=1,
 | 
			
		||||
            device_class=DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
@@ -60,14 +62,16 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_HECTOPASCAL,
 | 
			
		||||
            icon=ICON_GAUGE,
 | 
			
		||||
            accuracy_decimals=1,
 | 
			
		||||
            device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
 | 
			
		||||
            device_class=DEVICE_CLASS_PRESSURE,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ).extend(
 | 
			
		||||
            {cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_PERCENT,
 | 
			
		||||
            icon=ICON_WATER_PERCENT,
 | 
			
		||||
            accuracy_decimals=1,
 | 
			
		||||
            device_class=DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
@@ -93,14 +97,14 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_PARTS_PER_MILLION,
 | 
			
		||||
            icon=ICON_TEST_TUBE,
 | 
			
		||||
            accuracy_decimals=1,
 | 
			
		||||
            device_class=DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_PARTS_PER_MILLION,
 | 
			
		||||
            icon=ICON_TEST_TUBE,
 | 
			
		||||
            accuracy_decimals=1,
 | 
			
		||||
            device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -94,5 +94,3 @@ async def to_code(config):
 | 
			
		||||
        sens = await sensor.new_sensor(conf)
 | 
			
		||||
        cg.add(var.set_pressure_sensor(sens))
 | 
			
		||||
        cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_IDENTIFY,
 | 
			
		||||
    DEVICE_CLASS_RESTART,
 | 
			
		||||
    DEVICE_CLASS_UPDATE,
 | 
			
		||||
)
 | 
			
		||||
@@ -25,7 +24,6 @@ IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_IDENTIFY,
 | 
			
		||||
    DEVICE_CLASS_RESTART,
 | 
			
		||||
    DEVICE_CLASS_UPDATE,
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transm
 | 
			
		||||
  if (use_extended_id) {
 | 
			
		||||
    ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGD(TAG, "send standard id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
 | 
			
		||||
    ESP_LOGD(TAG, "send extended id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
 | 
			
		||||
  }
 | 
			
		||||
  if (size > CAN_MAX_DATA_LENGTH)
 | 
			
		||||
    size = CAN_MAX_DATA_LENGTH;
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.only_with_arduino,
 | 
			
		||||
    cv.only_on(["esp32", "esp8266"]),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -33,9 +34,8 @@ async def to_code(config):
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    cg.add_define("USE_CAPTIVE_PORTAL")
 | 
			
		||||
 | 
			
		||||
    if CORE.using_arduino:
 | 
			
		||||
        if CORE.is_esp32:
 | 
			
		||||
            cg.add_library("DNSServer", None)
 | 
			
		||||
            cg.add_library("WiFi", None)
 | 
			
		||||
        if CORE.is_esp8266:
 | 
			
		||||
            cg.add_library("DNSServer", None)
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        cg.add_library("DNSServer", None)
 | 
			
		||||
        cg.add_library("WiFi", None)
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        cg.add_library("DNSServer", None)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,100 +6,102 @@ namespace esphome {
 | 
			
		||||
namespace captive_portal {
 | 
			
		||||
 | 
			
		||||
const uint8_t INDEX_GZ[] PROGMEM = {
 | 
			
		||||
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xdd, 0x58, 0x5b, 0x8f, 0xdb, 0x36, 0x16, 0x7e, 0xef,
 | 
			
		||||
    0xaf, 0xe0, 0x2a, 0x49, 0x2d, 0x37, 0x23, 0xea, 0x66, 0xf9, 0x2a, 0xa9, 0x48, 0xb2, 0x29, 0x5a, 0x20, 0x69, 0x03,
 | 
			
		||||
    0xcc, 0xb4, 0xfb, 0x10, 0x04, 0x18, 0x5a, 0xa2, 0x2c, 0x66, 0x24, 0x4a, 0x15, 0xe9, 0x5b, 0x0c, 0xef, 0x6f, 0xdf,
 | 
			
		||||
    0x43, 0x52, 0xf6, 0x38, 0xb3, 0x99, 0x05, 0x52, 0xec, 0x62, 0xd1, 0x4e, 0x26, 0x1c, 0x92, 0x3a, 0xd7, 0x4f, 0x3c,
 | 
			
		||||
    0x17, 0x2a, 0xfe, 0x5b, 0xde, 0x64, 0x72, 0xdf, 0x52, 0x54, 0xca, 0xba, 0x4a, 0x63, 0x35, 0xa2, 0x8a, 0xf0, 0x55,
 | 
			
		||||
    0x42, 0x39, 0xac, 0x28, 0xc9, 0xd3, 0xb8, 0xa6, 0x92, 0xa0, 0xac, 0x24, 0x9d, 0xa0, 0x32, 0xf9, 0xf5, 0xe6, 0x07,
 | 
			
		||||
    0x67, 0x8a, 0xdc, 0x34, 0xae, 0x18, 0xbf, 0x43, 0x1d, 0xad, 0x12, 0x96, 0x35, 0x1c, 0x95, 0x1d, 0x2d, 0x92, 0x9c,
 | 
			
		||||
    0x48, 0x32, 0x67, 0x35, 0x59, 0x51, 0x45, 0xa0, 0xd9, 0x38, 0xa9, 0x69, 0xb2, 0x61, 0x74, 0xdb, 0x36, 0x9d, 0x44,
 | 
			
		||||
    0x40, 0x29, 0x29, 0x97, 0x89, 0xb5, 0x65, 0xb9, 0x2c, 0x93, 0x9c, 0x6e, 0x58, 0x46, 0x1d, 0xbd, 0xb8, 0x62, 0x9c,
 | 
			
		||||
    0x49, 0x46, 0x2a, 0x47, 0x64, 0xa4, 0xa2, 0x89, 0x7f, 0xb5, 0x16, 0xb4, 0xd3, 0x0b, 0xb2, 0x84, 0x35, 0x6f, 0x2c,
 | 
			
		||||
    0x10, 0x29, 0xb2, 0x8e, 0xb5, 0x12, 0x29, 0x7b, 0x93, 0xba, 0xc9, 0xd7, 0x15, 0x4d, 0x5d, 0x97, 0x08, 0xb0, 0x4b,
 | 
			
		||||
    0xb8, 0x8c, 0xe7, 0x74, 0x87, 0xa7, 0xb3, 0x68, 0x32, 0x9e, 0xe6, 0x13, 0xfc, 0x51, 0x7c, 0x03, 0x9e, 0xad, 0x6b,
 | 
			
		||||
    0x50, 0x87, 0xab, 0x26, 0x23, 0x92, 0x35, 0x1c, 0x0b, 0x4a, 0xba, 0xac, 0x4c, 0x92, 0xc4, 0xfa, 0x5e, 0x90, 0x0d,
 | 
			
		||||
    0xb5, 0xbe, 0xfd, 0xd6, 0x3e, 0x13, 0xad, 0xa8, 0x7c, 0x5d, 0x51, 0x35, 0x15, 0x2f, 0xf7, 0x37, 0x64, 0xf5, 0x33,
 | 
			
		||||
    0x58, 0x6e, 0x5b, 0x44, 0xb0, 0x9c, 0x5a, 0xc3, 0xf7, 0xde, 0x07, 0x2c, 0xe4, 0xbe, 0xa2, 0x38, 0x67, 0xa2, 0xad,
 | 
			
		||||
    0xc8, 0x3e, 0xb1, 0x96, 0x20, 0xf5, 0xce, 0x1a, 0x2e, 0x8a, 0x35, 0xcf, 0x94, 0x70, 0x24, 0x6c, 0x3a, 0x3c, 0x54,
 | 
			
		||||
    0x14, 0xcc, 0x4b, 0xde, 0x12, 0x59, 0xe2, 0x9a, 0xec, 0x6c, 0x33, 0x61, 0xdc, 0x0e, 0xbe, 0xb3, 0xe9, 0x73, 0xdf,
 | 
			
		||||
    0xf3, 0x86, 0x57, 0x7a, 0xf0, 0x86, 0x2e, 0xfc, 0x5d, 0x74, 0x54, 0xae, 0x3b, 0x8e, 0x88, 0x7d, 0x1b, 0xb7, 0x40,
 | 
			
		||||
    0x89, 0xf2, 0xc4, 0xaa, 0xfd, 0x00, 0x7b, 0xde, 0x14, 0xf9, 0x33, 0x1c, 0x44, 0x8e, 0xef, 0xe3, 0xd0, 0xf1, 0xa3,
 | 
			
		||||
    0x6c, 0xe2, 0x44, 0xc8, 0x1f, 0xc1, 0x10, 0x04, 0x38, 0x42, 0xde, 0x27, 0x0b, 0x15, 0xac, 0xaa, 0x12, 0x8b, 0x37,
 | 
			
		||||
    0x9c, 0x5a, 0x48, 0xc8, 0xae, 0xb9, 0xa3, 0x89, 0x95, 0xad, 0xbb, 0x0e, 0xec, 0x7f, 0xd5, 0x54, 0x4d, 0x07, 0x70,
 | 
			
		||||
    0x7d, 0x83, 0x3e, 0xfb, 0xf9, 0x6a, 0x15, 0xb2, 0x23, 0x5c, 0x14, 0x4d, 0x57, 0x27, 0x96, 0x7e, 0x29, 0xf6, 0xd3,
 | 
			
		||||
    0x83, 0x3c, 0x22, 0x35, 0x0c, 0x2f, 0x1e, 0x3a, 0x4d, 0xc7, 0x56, 0x8c, 0x27, 0x96, 0x1f, 0x20, 0x7f, 0x0a, 0x6a,
 | 
			
		||||
    0x6f, 0x87, 0xc7, 0x33, 0x26, 0x44, 0x61, 0xd2, 0x7b, 0xd9, 0xd8, 0xef, 0x6f, 0x63, 0xb1, 0x59, 0xa1, 0x5d, 0x5d,
 | 
			
		||||
    0x71, 0x91, 0x58, 0xa5, 0x94, 0xed, 0xdc, 0x75, 0xb7, 0xdb, 0x2d, 0xde, 0x86, 0xb8, 0xe9, 0x56, 0x6e, 0xe0, 0x79,
 | 
			
		||||
    0x9e, 0x0b, 0x14, 0x16, 0x32, 0xe7, 0xc3, 0x0a, 0x46, 0x16, 0x2a, 0x29, 0x5b, 0x95, 0x52, 0xcf, 0xd3, 0xa7, 0x07,
 | 
			
		||||
    0x7a, 0x8c, 0x15, 0x45, 0x7a, 0xfb, 0xe1, 0x42, 0x4b, 0x77, 0xa1, 0x85, 0x7e, 0x7f, 0x81, 0xe6, 0xe0, 0xad, 0x32,
 | 
			
		||||
    0x6a, 0x42, 0x02, 0x14, 0x20, 0x4f, 0xff, 0x0b, 0x1c, 0x35, 0xef, 0x57, 0xce, 0x83, 0x15, 0xba, 0x58, 0xc1, 0x5f,
 | 
			
		||||
    0xc0, 0x2f, 0xa8, 0xc7, 0xce, 0xec, 0xcc, 0xee, 0xab, 0xc7, 0x1b, 0xdf, 0xbb, 0xdf, 0x50, 0x3c, 0x3f, 0x8e, 0x2f,
 | 
			
		||||
    0xd7, 0x4e, 0xf0, 0x9b, 0x22, 0x50, 0xd8, 0x9f, 0x99, 0x9c, 0xa0, 0xf4, 0x7f, 0x1b, 0x93, 0x08, 0x45, 0xfd, 0x4e,
 | 
			
		||||
    0xe4, 0xa8, 0xf9, 0x79, 0xa5, 0x34, 0xa1, 0x68, 0x03, 0x54, 0xb5, 0x33, 0x76, 0x22, 0x12, 0xa2, 0xb0, 0x37, 0x09,
 | 
			
		||||
    0x66, 0xb0, 0x3d, 0x06, 0xe6, 0x8b, 0x3d, 0x27, 0xfc, 0x34, 0x50, 0x30, 0xcf, 0x2d, 0xeb, 0x1e, 0x83, 0xe6, 0x12,
 | 
			
		||||
    0x03, 0xfc, 0xb1, 0x81, 0x33, 0x67, 0x59, 0x80, 0x11, 0x95, 0x59, 0x69, 0x5b, 0x2e, 0x44, 0x5e, 0xc1, 0x56, 0x10,
 | 
			
		||||
    0x15, 0x0d, 0xb7, 0x86, 0x58, 0x96, 0x94, 0xdb, 0x27, 0x56, 0xc5, 0x48, 0xf5, 0x13, 0xfb, 0xe1, 0x13, 0x39, 0x3c,
 | 
			
		||||
    0x9c, 0xe3, 0x43, 0x32, 0x09, 0x71, 0x28, 0xb1, 0x8a, 0xe8, 0xab, 0xf3, 0xee, 0xb2, 0xc9, 0xf7, 0x8f, 0x84, 0x4e,
 | 
			
		||||
    0xe9, 0x9b, 0xb8, 0x61, 0x9c, 0xd3, 0xee, 0x86, 0xee, 0xe0, 0x1d, 0xfe, 0x83, 0xfd, 0xc0, 0xd0, 0xcf, 0x54, 0x6e,
 | 
			
		||||
    0x9b, 0xee, 0x4e, 0xcc, 0x91, 0xf5, 0xdc, 0x88, 0x5b, 0xa8, 0xa8, 0x61, 0x20, 0x9b, 0xb4, 0x02, 0x8b, 0x0a, 0x72,
 | 
			
		||||
    0x82, 0xed, 0x0f, 0x21, 0x7e, 0xda, 0x7b, 0x4b, 0xf8, 0xc9, 0xb9, 0xdb, 0x38, 0x67, 0x1b, 0x94, 0x55, 0x10, 0xf5,
 | 
			
		||||
    0x70, 0xfc, 0x8d, 0x28, 0x0b, 0xf5, 0x47, 0xbd, 0xe1, 0x19, 0x70, 0xdf, 0x25, 0xd6, 0x17, 0xa2, 0xfa, 0xe5, 0xfe,
 | 
			
		||||
    0xa7, 0xdc, 0x1e, 0x08, 0x88, 0xe7, 0xc1, 0x10, 0x6f, 0x48, 0xb5, 0xa6, 0x28, 0x41, 0xb2, 0x64, 0xe2, 0xde, 0xc0,
 | 
			
		||||
    0xc5, 0xa3, 0x6c, 0xad, 0xb8, 0x03, 0xae, 0x02, 0x1e, 0x0b, 0x7b, 0x68, 0x9d, 0x22, 0x2b, 0x26, 0x26, 0xef, 0x59,
 | 
			
		||||
    0x4f, 0xac, 0x07, 0x16, 0x39, 0x15, 0x2d, 0xa4, 0x75, 0x1f, 0x81, 0x4f, 0x0f, 0xc2, 0xe6, 0xb8, 0x03, 0xed, 0xc3,
 | 
			
		||||
    0xe3, 0x79, 0x33, 0x16, 0x2d, 0xe1, 0x0f, 0x19, 0x95, 0x81, 0xea, 0xa0, 0x43, 0xb2, 0x82, 0x99, 0x3a, 0xed, 0x40,
 | 
			
		||||
    0x74, 0x56, 0xe8, 0x92, 0xd3, 0xf4, 0xe9, 0xa1, 0x03, 0x89, 0x2a, 0x07, 0x9d, 0x25, 0xc6, 0x2e, 0x40, 0x93, 0xde,
 | 
			
		||||
    0x1e, 0x87, 0xf7, 0x7e, 0xfc, 0xbe, 0xa6, 0xdd, 0xfe, 0x9a, 0x56, 0x34, 0x93, 0x4d, 0x67, 0x5b, 0x4f, 0x40, 0x0b,
 | 
			
		||||
    0xbc, 0x7e, 0xed, 0xf0, 0x8f, 0x37, 0x6f, 0xdf, 0x24, 0x8d, 0xcd, 0x86, 0x57, 0x8f, 0x51, 0xab, 0x0c, 0xff, 0x1e,
 | 
			
		||||
    0x32, 0xfc, 0x3f, 0x93, 0x81, 0xca, 0xf1, 0x83, 0x0f, 0xc0, 0xaa, 0xfd, 0xbd, 0xbd, 0x4f, 0xf4, 0x2a, 0x18, 0x9f,
 | 
			
		||||
    0x43, 0x40, 0x5f, 0x29, 0x0f, 0x9d, 0x71, 0x34, 0x3c, 0x82, 0x7e, 0xb0, 0x00, 0xec, 0xd6, 0xb9, 0x1a, 0x72, 0xb6,
 | 
			
		||||
    0x4a, 0x9b, 0xe9, 0x77, 0x87, 0x65, 0xb3, 0x73, 0x04, 0xfb, 0xc4, 0xf8, 0x6a, 0xce, 0x78, 0x49, 0x3b, 0x26, 0x8f,
 | 
			
		||||
    0x60, 0x2e, 0xa4, 0xfd, 0x76, 0x2d, 0x0f, 0x2d, 0xc9, 0x73, 0xf5, 0x24, 0x6a, 0x77, 0x8b, 0x02, 0x8a, 0x84, 0xa2,
 | 
			
		||||
    0xa4, 0x73, 0x9f, 0xd6, 0x47, 0xf3, 0x5c, 0xe7, 0x83, 0xf9, 0x2c, 0x7a, 0x76, 0x54, 0x07, 0xee, 0x20, 0xe1, 0x65,
 | 
			
		||||
    0x39, 0xa4, 0x62, 0x2b, 0x3e, 0xcf, 0xc0, 0x70, 0xda, 0x19, 0xa6, 0x82, 0xd4, 0xac, 0xda, 0xcf, 0x05, 0x64, 0x26,
 | 
			
		||||
    0x07, 0xaa, 0x07, 0x2b, 0x8e, 0xcb, 0xb5, 0x94, 0x0d, 0x07, 0xdd, 0x5d, 0x4e, 0xbb, 0xb9, 0xb7, 0x30, 0x13, 0xa7,
 | 
			
		||||
    0x23, 0x39, 0x5b, 0x8b, 0x39, 0x0e, 0x3b, 0x5a, 0x2f, 0x96, 0x24, 0xbb, 0x5b, 0x75, 0xcd, 0x9a, 0xe7, 0x4e, 0xa6,
 | 
			
		||||
    0x32, 0xe7, 0xfc, 0x89, 0x5f, 0x90, 0x90, 0x66, 0x8b, 0x7e, 0x55, 0x14, 0xc5, 0x02, 0xa0, 0xa0, 0x8e, 0xc9, 0x44,
 | 
			
		||||
    0xf3, 0x00, 0x8f, 0x14, 0xdb, 0x85, 0x99, 0x38, 0x50, 0x1b, 0xc6, 0x46, 0x48, 0xeb, 0xcf, 0x16, 0x27, 0x77, 0xbc,
 | 
			
		||||
    0x05, 0xa4, 0x64, 0x01, 0x42, 0x5a, 0x88, 0x47, 0x30, 0xf3, 0x58, 0x13, 0xc6, 0x2f, 0xad, 0x57, 0xc7, 0x64, 0xd1,
 | 
			
		||||
    0x97, 0x14, 0x80, 0x45, 0xab, 0xd1, 0x85, 0x65, 0x01, 0x45, 0xc3, 0x14, 0xc6, 0x79, 0x30, 0xf6, 0xda, 0xdd, 0x11,
 | 
			
		||||
    0xf7, 0x07, 0xe4, 0x70, 0xa2, 0x2e, 0x2a, 0xba, 0x5b, 0x7c, 0x5c, 0x0b, 0xc9, 0x8a, 0xbd, 0xd3, 0x17, 0xd6, 0x39,
 | 
			
		||||
    0x1c, 0x16, 0x28, 0xa8, 0x4b, 0x20, 0xa5, 0x94, 0x2f, 0xb4, 0x0e, 0x87, 0x49, 0x5a, 0x8b, 0x1e, 0xa7, 0xb3, 0x18,
 | 
			
		||||
    0x7d, 0x40, 0x3f, 0x97, 0xf5, 0x9f, 0xa8, 0xd5, 0x59, 0x3c, 0xd4, 0xa4, 0x83, 0x44, 0xef, 0x2c, 0x1b, 0xc0, 0xb4,
 | 
			
		||||
    0x9e, 0x3b, 0x13, 0x78, 0x57, 0xfd, 0x96, 0x12, 0x06, 0x9e, 0x83, 0x99, 0xba, 0x5e, 0x9e, 0xf0, 0xf6, 0xdb, 0x1d,
 | 
			
		||||
    0x12, 0x4d, 0xc5, 0xf2, 0x9e, 0x4e, 0x93, 0x20, 0xef, 0x0c, 0x8f, 0x0f, 0xaf, 0x1b, 0xa9, 0xbd, 0x13, 0xd4, 0xa3,
 | 
			
		||||
    0x62, 0x4a, 0x7c, 0xef, 0x0b, 0x6f, 0x24, 0x2f, 0x8a, 0x60, 0x59, 0x9c, 0x91, 0x52, 0x65, 0xef, 0xc8, 0xfa, 0x53,
 | 
			
		||||
    0x11, 0x8c, 0x40, 0xc0, 0xe9, 0xdd, 0xc0, 0xfc, 0xc8, 0x74, 0x58, 0x1c, 0x2e, 0xa4, 0xe8, 0xa3, 0x3a, 0x5f, 0x77,
 | 
			
		||||
    0x95, 0x6d, 0x7d, 0xe1, 0xe8, 0x3e, 0x0b, 0x5f, 0xdd, 0x97, 0xa5, 0xc1, 0xe3, 0x65, 0x69, 0x80, 0x54, 0x23, 0xf3,
 | 
			
		||||
    0xb2, 0xd9, 0x25, 0x03, 0x5d, 0x20, 0x46, 0xf0, 0x3b, 0x78, 0x16, 0xbe, 0x06, 0xfe, 0xff, 0x4a, 0xbd, 0xf9, 0xc3,
 | 
			
		||||
    0xc5, 0xe6, 0x2b, 0x2a, 0xcd, 0x57, 0x56, 0x19, 0xe3, 0x9d, 0x72, 0x1e, 0x66, 0x50, 0x4e, 0x18, 0x16, 0x6c, 0xe5,
 | 
			
		||||
    0xff, 0x2f, 0xa0, 0xfd, 0x77, 0x1c, 0xc3, 0x17, 0xfe, 0x14, 0xcf, 0x90, 0x1e, 0x0c, 0x44, 0x38, 0x9c, 0xa2, 0xc9,
 | 
			
		||||
    0xab, 0x11, 0x1e, 0xf9, 0x48, 0xb5, 0x30, 0x63, 0x34, 0x81, 0x7e, 0x0f, 0xf9, 0x63, 0x1c, 0x4e, 0x60, 0x03, 0x05,
 | 
			
		||||
    0x3e, 0x8e, 0xde, 0x04, 0x21, 0x1e, 0x47, 0x40, 0x15, 0x78, 0x38, 0x0c, 0x90, 0xa1, 0x1d, 0xe3, 0x00, 0xc4, 0x29,
 | 
			
		||||
    0x92, 0xb0, 0x06, 0xa0, 0xb3, 0x10, 0x7b, 0x13, 0x10, 0x37, 0xc6, 0xde, 0x0c, 0x4f, 0xc7, 0x68, 0x8a, 0x27, 0x00,
 | 
			
		||||
    0x1d, 0x1e, 0x45, 0x95, 0x13, 0x61, 0x1f, 0xb6, 0xc3, 0x31, 0x99, 0xe2, 0x51, 0x88, 0xf4, 0x60, 0xe0, 0x98, 0x80,
 | 
			
		||||
    0x08, 0x07, 0x7b, 0xfe, 0x9b, 0x10, 0x07, 0x13, 0xd0, 0x3b, 0x1a, 0xbd, 0x00, 0xb1, 0xb3, 0x11, 0x32, 0xa3, 0x81,
 | 
			
		||||
    0x17, 0x14, 0x44, 0x8f, 0x81, 0x16, 0xfc, 0x75, 0x41, 0x03, 0x48, 0x7c, 0x14, 0xe2, 0x19, 0xc4, 0xae, 0xaf, 0xf8,
 | 
			
		||||
    0xcd, 0x68, 0x70, 0xf3, 0x7d, 0xe4, 0xfd, 0x61, 0xcc, 0xc2, 0xbf, 0x2e, 0x66, 0xbe, 0x42, 0x00, 0xa6, 0xa0, 0x1b,
 | 
			
		||||
    0xe4, 0x20, 0x3d, 0x18, 0xdd, 0xc0, 0x3c, 0x7d, 0x35, 0x43, 0x53, 0xe0, 0x1a, 0x4f, 0xd1, 0x0c, 0x45, 0x0a, 0x5d,
 | 
			
		||||
    0x60, 0x1f, 0x19, 0x26, 0x07, 0x98, 0xbe, 0x12, 0xc6, 0xd1, 0x9f, 0x18, 0xc6, 0xc7, 0x7c, 0xfa, 0x13, 0xbb, 0xf4,
 | 
			
		||||
    0xff, 0x48, 0x41, 0xd0, 0x8e, 0xe9, 0x36, 0x2c, 0x76, 0xcd, 0x95, 0x5e, 0x75, 0x51, 0x70, 0x43, 0x87, 0x6e, 0x04,
 | 
			
		||||
    0x2e, 0xf9, 0x3e, 0x62, 0x79, 0x52, 0xfa, 0xe9, 0x67, 0xdd, 0x39, 0x50, 0xfa, 0x69, 0xac, 0xcb, 0x79, 0x7a, 0x53,
 | 
			
		||||
    0x52, 0xf4, 0xfa, 0xfa, 0x1d, 0xdc, 0xca, 0xaa, 0x0a, 0xf1, 0x66, 0x0b, 0x97, 0xbf, 0x3d, 0x92, 0x8d, 0xba, 0xce,
 | 
			
		||||
    0x73, 0xe8, 0x15, 0xd5, 0x14, 0xee, 0x0d, 0xa8, 0x6f, 0x16, 0x30, 0xc6, 0xf1, 0xb2, 0x4b, 0xdf, 0x55, 0x94, 0x08,
 | 
			
		||||
    0x8a, 0x56, 0x6c, 0x43, 0x11, 0x93, 0xd0, 0x07, 0xd4, 0x14, 0x49, 0xa6, 0x86, 0x33, 0xa3, 0xa6, 0x83, 0x9e, 0x56,
 | 
			
		||||
    0x2b, 0x31, 0xdd, 0x30, 0x58, 0x02, 0x62, 0xd2, 0xbe, 0xed, 0x8d, 0xcb, 0xd0, 0x58, 0x75, 0x4d, 0xa5, 0x84, 0x8e,
 | 
			
		||||
    0x41, 0x59, 0x15, 0xa6, 0xb1, 0xba, 0x76, 0x22, 0xa2, 0x2f, 0x06, 0x89, 0xbb, 0x65, 0x05, 0x53, 0x97, 0xf9, 0x34,
 | 
			
		||||
    0xd6, 0xad, 0xa2, 0x92, 0xa0, 0xba, 0x15, 0xf3, 0xe5, 0x41, 0xcf, 0x2a, 0xca, 0x57, 0x70, 0x9b, 0x84, 0x77, 0x01,
 | 
			
		||||
    0xcd, 0x43, 0x46, 0xcb, 0xa6, 0x82, 0xe6, 0x24, 0xb9, 0xbe, 0xfe, 0xe9, 0xef, 0xea, 0x33, 0x85, 0x32, 0xe1, 0xcc,
 | 
			
		||||
    0x09, 0x7d, 0xbe, 0x61, 0x54, 0x93, 0x9e, 0x6f, 0x3c, 0x32, 0x1f, 0x1c, 0x5a, 0xe8, 0xd3, 0xc1, 0xbf, 0xfc, 0x33,
 | 
			
		||||
    0x29, 0xef, 0x4e, 0x9b, 0xbd, 0x24, 0xfd, 0x5f, 0x37, 0x9d, 0x86, 0x49, 0xac, 0x97, 0x35, 0x93, 0xe9, 0x35, 0x18,
 | 
			
		||||
    0x18, 0xbb, 0xe6, 0x01, 0x38, 0xa7, 0x1c, 0x30, 0xb4, 0x65, 0xcf, 0x03, 0x60, 0xff, 0x72, 0xf3, 0x02, 0xfd, 0xda,
 | 
			
		||||
    0xc2, 0x09, 0xa6, 0x06, 0x7b, 0xed, 0x65, 0x4d, 0x65, 0xd9, 0xe4, 0xc9, 0xbb, 0x5f, 0xae, 0x6f, 0xce, 0x1e, 0xaf,
 | 
			
		||||
    0x35, 0x11, 0xa2, 0x3c, 0x33, 0x1f, 0x42, 0xd6, 0x95, 0x64, 0x2d, 0xe9, 0xa4, 0x16, 0xeb, 0xa8, 0x10, 0x38, 0x79,
 | 
			
		||||
    0xa4, 0x9f, 0x17, 0xac, 0xa2, 0xc6, 0xa9, 0x9e, 0xd1, 0x4d, 0xd1, 0x97, 0x6c, 0x3c, 0xe9, 0x7e, 0x60, 0xa5, 0x6b,
 | 
			
		||||
    0x4e, 0x89, 0x6b, 0x8e, 0x8c, 0xab, 0x3f, 0x13, 0xfd, 0x0b, 0x65, 0x37, 0xa3, 0x8e, 0x36, 0x12, 0x00, 0x00};
 | 
			
		||||
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xdd, 0x58, 0x09, 0x6f, 0xdc, 0x36, 0x16, 0xfe, 0x2b,
 | 
			
		||||
    0xac, 0x92, 0x74, 0x34, 0x8d, 0xc5, 0xd1, 0x31, 0x97, 0x35, 0xd2, 0x14, 0x89, 0x37, 0x45, 0x0b, 0x24, 0x69, 0x00,
 | 
			
		||||
    0xbb, 0x5d, 0x14, 0x69, 0x00, 0x73, 0x24, 0x6a, 0xc4, 0x58, 0xa2, 0x54, 0x91, 0x9a, 0x23, 0x83, 0xd9, 0xdf, 0xde,
 | 
			
		||||
    0x47, 0x52, 0x73, 0x38, 0x6b, 0x2f, 0x90, 0x62, 0x8b, 0xa2, 0x4d, 0x6c, 0x9a, 0xc7, 0x3b, 0x3f, 0xf2, 0xf1, 0x3d,
 | 
			
		||||
    0x2a, 0xfa, 0x2a, 0xad, 0x12, 0xb9, 0xad, 0x29, 0xca, 0x65, 0x59, 0xcc, 0x23, 0xd5, 0xa2, 0x82, 0xf0, 0x65, 0x4c,
 | 
			
		||||
    0x39, 0x8c, 0x28, 0x49, 0xe7, 0x51, 0x49, 0x25, 0x41, 0x49, 0x4e, 0x1a, 0x41, 0x65, 0xfc, 0xd3, 0xcd, 0x77, 0xce,
 | 
			
		||||
    0x14, 0x0d, 0xe6, 0x51, 0xc1, 0xf8, 0x1d, 0x6a, 0x68, 0x11, 0xb3, 0xa4, 0xe2, 0x28, 0x6f, 0x68, 0x16, 0xa7, 0x44,
 | 
			
		||||
    0x92, 0x90, 0x95, 0x64, 0x49, 0x15, 0x81, 0x66, 0xe3, 0xa4, 0xa4, 0xf1, 0x8a, 0xd1, 0x75, 0x5d, 0x35, 0x12, 0x01,
 | 
			
		||||
    0xa5, 0xa4, 0x5c, 0xc6, 0xd6, 0x9a, 0xa5, 0x32, 0x8f, 0x53, 0xba, 0x62, 0x09, 0x75, 0xf4, 0xe0, 0x82, 0x71, 0x26,
 | 
			
		||||
    0x19, 0x29, 0x1c, 0x91, 0x90, 0x82, 0xc6, 0xde, 0x45, 0x2b, 0x68, 0xa3, 0x07, 0x64, 0x01, 0x63, 0x5e, 0x59, 0x20,
 | 
			
		||||
    0x52, 0x24, 0x0d, 0xab, 0x25, 0x52, 0xf6, 0xc6, 0x65, 0x95, 0xb6, 0x05, 0x9d, 0x67, 0x2d, 0x4f, 0x24, 0x03, 0x0b,
 | 
			
		||||
    0x84, 0xcd, 0xfb, 0xbb, 0x82, 0x4a, 0x44, 0xe3, 0x37, 0x44, 0xe6, 0xb8, 0x24, 0x1b, 0xdb, 0x74, 0x18, 0xb7, 0xfd,
 | 
			
		||||
    0x6f, 0x6c, 0xfe, 0xdc, 0x73, 0xdd, 0xfe, 0x85, 0x6e, 0xdc, 0xfe, 0x00, 0xfe, 0xce, 0x1a, 0x2a, 0xdb, 0x86, 0x23,
 | 
			
		||||
    0x62, 0xdf, 0x46, 0x35, 0x50, 0xa2, 0x34, 0xb6, 0x4a, 0xcf, 0xc7, 0xae, 0x3b, 0x45, 0xde, 0x25, 0xf6, 0x47, 0x8e,
 | 
			
		||||
    0xe7, 0xe1, 0xc0, 0xf1, 0x46, 0xc9, 0xc4, 0x19, 0x21, 0x6f, 0x08, 0x8d, 0xef, 0xe3, 0x11, 0x72, 0x3f, 0x59, 0x28,
 | 
			
		||||
    0x63, 0x45, 0x11, 0x5b, 0xbc, 0xe2, 0xd4, 0x42, 0x42, 0x36, 0xd5, 0x1d, 0x8d, 0xad, 0xa4, 0x6d, 0x1a, 0xf0, 0xee,
 | 
			
		||||
    0xaa, 0x2a, 0xaa, 0x06, 0xac, 0xfd, 0x95, 0xa3, 0x7b, 0xff, 0xbe, 0x58, 0x87, 0x6c, 0x08, 0x17, 0x59, 0xd5, 0x94,
 | 
			
		||||
    0xb1, 0xa5, 0x41, 0xb1, 0x9f, 0xee, 0xe8, 0x1e, 0xa9, 0xa6, 0x7f, 0xb6, 0xe8, 0x54, 0x0d, 0x5b, 0x32, 0x1e, 0x5b,
 | 
			
		||||
    0x9e, 0x8f, 0xbc, 0x29, 0xe8, 0xbd, 0xed, 0xef, 0x8f, 0xa0, 0x10, 0x05, 0x4a, 0xe7, 0x66, 0x65, 0xbf, 0xbf, 0x8d,
 | 
			
		||||
    0xc4, 0x6a, 0x89, 0x36, 0x65, 0xc1, 0x45, 0x6c, 0xe5, 0x52, 0xd6, 0xe1, 0x60, 0xb0, 0x5e, 0xaf, 0xf1, 0x3a, 0xc0,
 | 
			
		||||
    0x55, 0xb3, 0x1c, 0xf8, 0xae, 0xeb, 0x0e, 0x80, 0xc2, 0x42, 0x66, 0x7f, 0x2c, 0x7f, 0x68, 0xa1, 0x9c, 0xb2, 0x65,
 | 
			
		||||
    0x2e, 0x75, 0x7f, 0xfe, 0x74, 0xc7, 0xf7, 0x91, 0xa2, 0x98, 0xdf, 0x7e, 0x38, 0xd3, 0xd2, 0x9c, 0x69, 0xe1, 0xdf,
 | 
			
		||||
    0x12, 0xdb, 0x3a, 0xb8, 0xda, 0x7b, 0xa3, 0x8c, 0x9a, 0x10, 0x1f, 0xf9, 0xc8, 0xd5, 0xff, 0x7d, 0x47, 0xf5, 0xbb,
 | 
			
		||||
    0x91, 0xf3, 0xd9, 0x08, 0x9d, 0x8d, 0xe0, 0xaf, 0x02, 0xd0, 0x2f, 0xc7, 0xce, 0xe5, 0x91, 0xdf, 0x53, 0xeb, 0x2b,
 | 
			
		||||
    0xcf, 0x3d, 0x4d, 0x28, 0xa6, 0xef, 0xc7, 0xe7, 0x63, 0xc7, 0xff, 0x59, 0x11, 0x68, 0xf4, 0x8f, 0x5c, 0x8e, 0x9f,
 | 
			
		||||
    0x7b, 0x3f, 0x8f, 0xc9, 0x08, 0x8d, 0xba, 0x99, 0x91, 0xa3, 0xfa, 0xc7, 0x91, 0xd6, 0x85, 0x46, 0x2b, 0x20, 0x2b,
 | 
			
		||||
    0x9d, 0xb1, 0x33, 0x22, 0x01, 0x0a, 0x3a, 0xab, 0xa0, 0x07, 0xd3, 0x63, 0xe0, 0x3e, 0x9b, 0x73, 0x82, 0x4f, 0xbd,
 | 
			
		||||
    0xc1, 0xdc, 0xea, 0x87, 0x96, 0x75, 0x82, 0xa1, 0x3a, 0x87, 0x01, 0x7f, 0xac, 0xe0, 0xdc, 0x59, 0x56, 0x7f, 0x6f,
 | 
			
		||||
    0x7d, 0x2b, 0xc8, 0x8a, 0x5a, 0x71, 0x1c, 0x43, 0xa8, 0xb5, 0x25, 0x9c, 0x10, 0x5c, 0x54, 0x09, 0x51, 0x2c, 0x58,
 | 
			
		||||
    0x50, 0xd2, 0x24, 0xf9, 0xd7, 0x5f, 0xdb, 0xc7, 0xa5, 0x25, 0x95, 0xaf, 0x0a, 0xaa, 0xba, 0xe2, 0xe5, 0xf6, 0x86,
 | 
			
		||||
    0x2c, 0xdf, 0x42, 0x00, 0xd9, 0x16, 0x11, 0x2c, 0xa5, 0x56, 0xff, 0xbd, 0xfb, 0x01, 0x0b, 0xb9, 0x2d, 0x28, 0x4e,
 | 
			
		||||
    0x99, 0xa8, 0x0b, 0xb2, 0x8d, 0xad, 0x05, 0xc8, 0xba, 0xb3, 0xfa, 0x17, 0x19, 0x95, 0x49, 0x6e, 0x5b, 0x03, 0x08,
 | 
			
		||||
    0xb1, 0x8c, 0x2d, 0xf1, 0x47, 0x51, 0x71, 0xab, 0x8f, 0x65, 0x4e, 0xb9, 0x6d, 0x1f, 0x2c, 0x54, 0xf6, 0x71, 0xbd,
 | 
			
		||||
    0x64, 0x3f, 0xb4, 0x74, 0xb4, 0x41, 0x32, 0xa9, 0x42, 0x0e, 0xab, 0xe0, 0xbd, 0x38, 0xce, 0x2e, 0xaa, 0x74, 0xfb,
 | 
			
		||||
    0x88, 0x79, 0xb9, 0x67, 0x6c, 0x63, 0x9c, 0xd3, 0xe6, 0x86, 0x6e, 0xe0, 0xb8, 0xfc, 0x9b, 0x7d, 0xc7, 0xd0, 0x5b,
 | 
			
		||||
    0x2a, 0xd7, 0x55, 0x73, 0x27, 0x42, 0x64, 0x3d, 0x37, 0xe2, 0x66, 0x26, 0x42, 0x39, 0x26, 0xb5, 0xc0, 0xa2, 0x80,
 | 
			
		||||
    0xf0, 0xb7, 0xbd, 0x3e, 0xc4, 0x6a, 0x7d, 0xdf, 0x14, 0x83, 0xe2, 0x6d, 0x94, 0xb2, 0x15, 0x4a, 0x0a, 0x22, 0xe0,
 | 
			
		||||
    0xb8, 0x72, 0x23, 0xcb, 0x42, 0x87, 0xb8, 0xaa, 0x78, 0x02, 0xfc, 0x77, 0xb1, 0xf5, 0x00, 0x76, 0x2f, 0xb7, 0x3f,
 | 
			
		||||
    0xa4, 0x76, 0x4f, 0x00, 0x6a, 0xbd, 0x3e, 0x5e, 0x91, 0xa2, 0xa5, 0x28, 0x46, 0x32, 0x67, 0xe2, 0x64, 0xe2, 0xec,
 | 
			
		||||
    0x51, 0xb6, 0x5a, 0xdc, 0x01, 0x57, 0x06, 0xcb, 0xc2, 0xee, 0x5b, 0xc7, 0x38, 0x8e, 0x88, 0xb9, 0xe5, 0xac, 0x27,
 | 
			
		||||
    0xd6, 0x67, 0x36, 0x39, 0x05, 0xcd, 0xa4, 0x75, 0x16, 0xf0, 0x4f, 0x77, 0x70, 0x1b, 0xe1, 0x06, 0xf4, 0xf7, 0xf7,
 | 
			
		||||
    0xa7, 0xd9, 0x48, 0xd4, 0x84, 0x7f, 0xce, 0xaa, 0x6c, 0xd4, 0x81, 0x85, 0x55, 0x4f, 0x45, 0x17, 0x10, 0x9d, 0x74,
 | 
			
		||||
    0x0e, 0xc8, 0xb1, 0xff, 0x74, 0x07, 0x71, 0xa6, 0x8e, 0xce, 0xdd, 0x49, 0x68, 0x34, 0x00, 0x84, 0xe6, 0xb7, 0xfb,
 | 
			
		||||
    0x7e, 0xff, 0xe4, 0xce, 0x6f, 0x2d, 0x6d, 0xb6, 0xd7, 0xb4, 0xa0, 0x89, 0xac, 0x1a, 0xdb, 0x7a, 0x02, 0x9a, 0xe0,
 | 
			
		||||
    0x24, 0x68, 0xbf, 0xbf, 0xbf, 0x79, 0xf3, 0x3a, 0xae, 0x6c, 0xda, 0xbf, 0x78, 0x8c, 0x5a, 0xdd, 0xea, 0xef, 0xe1,
 | 
			
		||||
    0x56, 0xff, 0x4f, 0xdc, 0x53, 0xf7, 0x7a, 0xef, 0x03, 0xb0, 0x1a, 0xaf, 0x4f, 0x97, 0xbb, 0xba, 0x00, 0x9e, 0xc3,
 | 
			
		||||
    0x25, 0x72, 0x61, 0x3d, 0x17, 0xb6, 0x33, 0x1e, 0xf5, 0x41, 0x3d, 0xfc, 0x80, 0xe9, 0xfa, 0x7a, 0x86, 0x6b, 0x5a,
 | 
			
		||||
    0x1d, 0xd1, 0xf9, 0x37, 0xbb, 0x45, 0xb5, 0x71, 0x04, 0xfb, 0xc4, 0xf8, 0x32, 0x64, 0x3c, 0xa7, 0x0d, 0x93, 0x7b,
 | 
			
		||||
    0x30, 0x17, 0x6e, 0xfa, 0xba, 0x95, 0xbb, 0x9a, 0xa4, 0xa9, 0x5a, 0x19, 0xd5, 0x9b, 0x59, 0x06, 0x79, 0x41, 0x51,
 | 
			
		||||
    0xd2, 0xd0, 0xa3, 0xe5, 0xde, 0xac, 0xeb, 0x2b, 0x28, 0xbc, 0x1c, 0x3d, 0xdb, 0xab, 0x83, 0xb7, 0x93, 0xb0, 0x65,
 | 
			
		||||
    0x0e, 0x29, 0xd8, 0x92, 0x87, 0x09, 0xd8, 0x4d, 0x1b, 0xc3, 0x94, 0x91, 0x92, 0x15, 0xdb, 0x50, 0xc0, 0x65, 0xe8,
 | 
			
		||||
    0x40, 0xc2, 0x60, 0xd9, 0x7e, 0xd1, 0x4a, 0x59, 0x71, 0xd0, 0xdd, 0xa4, 0xb4, 0x09, 0xdd, 0x99, 0xe9, 0x38, 0x0d,
 | 
			
		||||
    0x49, 0x59, 0x2b, 0x42, 0x1c, 0x34, 0xb4, 0x9c, 0x2d, 0x48, 0x72, 0xb7, 0x6c, 0xaa, 0x96, 0xa7, 0x4e, 0xa2, 0x6e,
 | 
			
		||||
    0xeb, 0xf0, 0x89, 0x97, 0x91, 0x80, 0x26, 0xb3, 0x6e, 0x94, 0x65, 0xd9, 0x0c, 0x90, 0xa0, 0x8e, 0xb9, 0xfc, 0x42,
 | 
			
		||||
    0x1f, 0x0f, 0x15, 0xdb, 0x99, 0x99, 0xd8, 0x57, 0x13, 0xc6, 0x46, 0x48, 0x25, 0xcf, 0x66, 0x07, 0x77, 0xdc, 0x19,
 | 
			
		||||
    0xa4, 0x01, 0x01, 0x42, 0x6a, 0x88, 0x7f, 0x30, 0x73, 0x5f, 0x12, 0xc6, 0xcf, 0xad, 0x57, 0x67, 0x65, 0xd6, 0x85,
 | 
			
		||||
    0x2f, 0xc0, 0xa2, 0xd5, 0xe8, 0x20, 0x9e, 0x41, 0xa2, 0x32, 0xb9, 0x30, 0xf4, 0xc7, 0x6e, 0xbd, 0xd9, 0xe3, 0xee,
 | 
			
		||||
    0x8c, 0xec, 0x0e, 0xd4, 0x59, 0x41, 0x37, 0xb3, 0x8f, 0xad, 0x90, 0x2c, 0xdb, 0x3a, 0x5d, 0x2e, 0x0d, 0xe1, 0xbc,
 | 
			
		||||
    0x40, 0x0e, 0x5d, 0x00, 0x29, 0xa5, 0x7c, 0xa6, 0x75, 0x38, 0x4c, 0xd2, 0x52, 0x74, 0x38, 0x1d, 0xc5, 0xe8, 0x53,
 | 
			
		||||
    0x7a, 0x5f, 0xd6, 0xff, 0xa2, 0x56, 0xc7, 0x71, 0x57, 0x92, 0x06, 0x72, 0x8b, 0xb3, 0xa8, 0x00, 0xd3, 0x32, 0x74,
 | 
			
		||||
    0x26, 0xb0, 0x57, 0xdd, 0x94, 0x12, 0x06, 0x9e, 0x83, 0x99, 0xfa, 0x6e, 0x3a, 0xe0, 0xed, 0xd5, 0x1b, 0x24, 0xaa,
 | 
			
		||||
    0x82, 0xa5, 0x1d, 0x9d, 0x26, 0x41, 0xee, 0x11, 0x1e, 0x0f, 0xb6, 0x1b, 0xa9, 0xb9, 0x03, 0xd4, 0xc3, 0x6c, 0x4a,
 | 
			
		||||
    0x3c, 0xf7, 0x81, 0x1d, 0x49, 0xb3, 0xcc, 0x5f, 0x64, 0x47, 0xa4, 0x54, 0xaa, 0xdd, 0xb3, 0xee, 0x54, 0xf8, 0x43,
 | 
			
		||||
    0x10, 0x70, 0xd8, 0x1b, 0xe8, 0xef, 0x99, 0x8e, 0x8b, 0xdd, 0x99, 0x14, 0x7d, 0x52, 0xc3, 0xb6, 0x29, 0xec, 0x87,
 | 
			
		||||
    0x4e, 0xee, 0xb3, 0xe0, 0xea, 0x94, 0x09, 0x7b, 0x8f, 0x67, 0xc2, 0x1e, 0x52, 0xb5, 0xcb, 0xcb, 0x6a, 0x13, 0xf7,
 | 
			
		||||
    0x74, 0x4e, 0x1a, 0xc2, 0x4f, 0xef, 0x59, 0xf0, 0x0a, 0xf8, 0xff, 0x2f, 0x29, 0xee, 0x0f, 0xa7, 0xb7, 0x2f, 0x48,
 | 
			
		||||
    0x6d, 0x5f, 0x98, 0xd5, 0x8c, 0x77, 0xca, 0x79, 0xe8, 0x41, 0xfa, 0x62, 0x58, 0xb0, 0xa5, 0xf7, 0x67, 0x40, 0xfb,
 | 
			
		||||
    0xdf, 0x38, 0x06, 0x2f, 0xbc, 0x29, 0xbe, 0x44, 0xba, 0x31, 0x10, 0xe1, 0x60, 0x8a, 0x26, 0x57, 0x43, 0x3c, 0xf4,
 | 
			
		||||
    0x90, 0xaa, 0x9a, 0xc6, 0x68, 0x82, 0xa7, 0x40, 0x30, 0xc6, 0xc1, 0x04, 0x26, 0x90, 0xef, 0xe1, 0xd1, 0x6b, 0x3f,
 | 
			
		||||
    0xc0, 0xe3, 0x11, 0x50, 0xf9, 0x2e, 0x0e, 0x7c, 0x64, 0x68, 0xc7, 0xd8, 0x07, 0x71, 0x8a, 0x24, 0x28, 0x01, 0xe8,
 | 
			
		||||
    0x24, 0xc0, 0xee, 0x04, 0xc4, 0x8d, 0xb1, 0x7b, 0x89, 0xa7, 0x63, 0x34, 0xc5, 0x13, 0x80, 0x0e, 0x0f, 0x47, 0x85,
 | 
			
		||||
    0x33, 0xc2, 0x1e, 0x4c, 0x07, 0x63, 0x32, 0xc5, 0xc3, 0x00, 0xe9, 0xc6, 0xc0, 0x31, 0x01, 0x11, 0x0e, 0x76, 0xbd,
 | 
			
		||||
    0xd7, 0x01, 0xf6, 0x27, 0xa0, 0x77, 0x38, 0x7c, 0x01, 0x62, 0x2f, 0x87, 0xc8, 0xb4, 0x06, 0x5e, 0x50, 0x30, 0x7a,
 | 
			
		||||
    0x0c, 0x34, 0xff, 0x9f, 0x0b, 0x1a, 0x40, 0xe2, 0xa1, 0x00, 0x5f, 0x42, 0xec, 0x7a, 0x8a, 0xdf, 0xb4, 0x06, 0x37,
 | 
			
		||||
    0xcf, 0x43, 0xee, 0x1f, 0xc6, 0x2c, 0xf8, 0xe7, 0x62, 0xe6, 0x29, 0x04, 0xa0, 0x0b, 0xba, 0x41, 0x0e, 0xd2, 0x8d,
 | 
			
		||||
    0xd1, 0x0d, 0xcc, 0xd3, 0xab, 0x4b, 0x34, 0x05, 0xae, 0xf1, 0x14, 0x5d, 0xa2, 0x91, 0x42, 0x17, 0xd8, 0x87, 0x86,
 | 
			
		||||
    0xc9, 0x01, 0xa6, 0x2f, 0x84, 0x71, 0xf8, 0x37, 0x86, 0xf1, 0x31, 0x9f, 0xfe, 0xc6, 0x2e, 0xfd, 0x15, 0x57, 0x10,
 | 
			
		||||
    0x94, 0x63, 0xba, 0x0c, 0x8b, 0x06, 0xe6, 0x15, 0xaf, 0xaa, 0x28, 0x78, 0x94, 0x43, 0x35, 0x02, 0xef, 0x7a, 0x0f,
 | 
			
		||||
    0xb1, 0x34, 0xce, 0xbd, 0xf9, 0xbd, 0x2a, 0x1d, 0x28, 0xbd, 0x79, 0xa4, 0xd3, 0xf9, 0xfc, 0x26, 0xa7, 0xe8, 0xd5,
 | 
			
		||||
    0xf5, 0x3b, 0x78, 0x08, 0x16, 0x05, 0xe2, 0xd5, 0x1a, 0xde, 0x9b, 0x5b, 0x24, 0x2b, 0xf5, 0x82, 0xe7, 0x50, 0x2a,
 | 
			
		||||
    0xaa, 0x2e, 0x3c, 0x20, 0x50, 0x57, 0x2c, 0x60, 0x8c, 0xa3, 0x45, 0x33, 0x7f, 0x57, 0x50, 0x22, 0x28, 0x5a, 0xb2,
 | 
			
		||||
    0x15, 0x45, 0x4c, 0x42, 0x1d, 0x50, 0x52, 0x24, 0x99, 0x6a, 0x8e, 0x8c, 0x9a, 0xee, 0x6d, 0x25, 0x69, 0x88, 0xae,
 | 
			
		||||
    0xaa, 0x7a, 0xab, 0x85, 0x24, 0x39, 0xe1, 0x4b, 0x9a, 0x1e, 0x84, 0x29, 0xea, 0x6d, 0xd5, 0x36, 0xe8, 0x97, 0x17,
 | 
			
		||||
    0x6f, 0x5e, 0xab, 0x87, 0x36, 0x45, 0x4e, 0xa7, 0x6c, 0x23, 0xd1, 0x8f, 0x37, 0x2f, 0x50, 0x5b, 0xc3, 0xa6, 0x53,
 | 
			
		||||
    0x63, 0x5b, 0xb5, 0xa2, 0xcd, 0x1a, 0x2a, 0x4b, 0xaa, 0x48, 0x40, 0xb9, 0xa0, 0x52, 0x42, 0xa1, 0x21, 0x30, 0x94,
 | 
			
		||||
    0xce, 0xda, 0x13, 0x53, 0x75, 0x83, 0xbb, 0x20, 0x7e, 0xde, 0x95, 0xd7, 0x51, 0x1e, 0x18, 0xd7, 0xaf, 0x3b, 0x6a,
 | 
			
		||||
    0x70, 0x3d, 0x98, 0x47, 0xea, 0x39, 0x8d, 0x88, 0x7e, 0x84, 0xc4, 0x83, 0x35, 0xcb, 0x98, 0x7a, 0xb8, 0xcd, 0x23,
 | 
			
		||||
    0x5d, 0x8f, 0x2a, 0x09, 0xaa, 0x24, 0x32, 0x5f, 0x34, 0x74, 0xaf, 0xa0, 0x7c, 0x09, 0xaf, 0x64, 0xd8, 0x70, 0xa8,
 | 
			
		||||
    0x50, 0x12, 0x9a, 0x57, 0x05, 0x54, 0x40, 0xf1, 0xf5, 0xf5, 0x0f, 0xff, 0x52, 0x9f, 0x3f, 0xc0, 0xcf, 0x13, 0x27,
 | 
			
		||||
    0x3c, 0x29, 0x0c, 0xa3, 0xea, 0x74, 0x7c, 0xe3, 0xa1, 0xf9, 0x90, 0x51, 0xc3, 0x7b, 0x00, 0xfc, 0x4e, 0xef, 0x49,
 | 
			
		||||
    0x79, 0x77, 0x98, 0xec, 0x24, 0xe9, 0x5f, 0x5d, 0xd9, 0x1a, 0x26, 0xd1, 0x2e, 0x4a, 0x26, 0xe7, 0xd7, 0x60, 0x60,
 | 
			
		||||
    0x34, 0x30, 0x0b, 0xe0, 0x9c, 0x72, 0xc0, 0xd0, 0xe6, 0x1d, 0x0f, 0xec, 0xa8, 0x42, 0xec, 0x27, 0x8d, 0x98, 0xd9,
 | 
			
		||||
    0x60, 0xed, 0x65, 0x49, 0x65, 0x5e, 0xa5, 0xf1, 0xbb, 0x1f, 0xaf, 0x6f, 0x8e, 0x1e, 0x77, 0xb0, 0x52, 0x9e, 0x98,
 | 
			
		||||
    0x0f, 0x2c, 0x6d, 0x21, 0x59, 0x4d, 0x1a, 0xa9, 0xc5, 0x3a, 0x2a, 0xce, 0x0e, 0x1e, 0xe9, 0x75, 0xbd, 0x33, 0xda,
 | 
			
		||||
    0xa9, 0x8e, 0x71, 0x30, 0x47, 0x0f, 0xd9, 0x78, 0xd0, 0xfd, 0x99, 0x95, 0x03, 0x73, 0x14, 0x07, 0xe6, 0x5c, 0x0e,
 | 
			
		||||
    0xf4, 0xe7, 0xa7, 0xdf, 0x01, 0xf1, 0x69, 0xfc, 0xac, 0x8e, 0x12, 0x00, 0x00};
 | 
			
		||||
 | 
			
		||||
}  // namespace captive_portal
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include "captive_portal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
@@ -44,12 +46,10 @@ void CaptivePortal::start() {
 | 
			
		||||
    this->base_->add_ota_handler();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
  this->dns_server_ = make_unique<DNSServer>();
 | 
			
		||||
  this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
 | 
			
		||||
  network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
 | 
			
		||||
  this->dns_server_->start(53, "*", (uint32_t) ip);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
 | 
			
		||||
    if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
 | 
			
		||||
@@ -67,7 +67,7 @@ void CaptivePortal::start() {
 | 
			
		||||
 | 
			
		||||
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
 | 
			
		||||
  if (req->url() == "/") {
 | 
			
		||||
    auto *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
 | 
			
		||||
    AsyncWebServerResponse *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
 | 
			
		||||
    response->addHeader("Content-Encoding", "gzip");
 | 
			
		||||
    req->send(response);
 | 
			
		||||
    return;
 | 
			
		||||
@@ -91,3 +91,5 @@ CaptivePortal *global_captive_portal = nullptr;  // NOLINT(cppcoreguidelines-avo
 | 
			
		||||
 | 
			
		||||
}  // namespace captive_portal
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <DNSServer.h>
 | 
			
		||||
#endif
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/preferences.h"
 | 
			
		||||
@@ -18,22 +18,18 @@ class CaptivePortal : public AsyncWebHandler, public Component {
 | 
			
		||||
  CaptivePortal(web_server_base::WebServerBase *base);
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
  void loop() override {
 | 
			
		||||
    if (this->dns_server_ != nullptr)
 | 
			
		||||
      this->dns_server_->processNextRequest();
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void start();
 | 
			
		||||
  bool is_active() const { return this->active_; }
 | 
			
		||||
  void end() {
 | 
			
		||||
    this->active_ = false;
 | 
			
		||||
    this->base_->deinit();
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
    this->dns_server_->stop();
 | 
			
		||||
    this->dns_server_ = nullptr;
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool canHandle(AsyncWebServerRequest *request) override {
 | 
			
		||||
@@ -62,12 +58,12 @@ class CaptivePortal : public AsyncWebHandler, public Component {
 | 
			
		||||
  web_server_base::WebServerBase *base_;
 | 
			
		||||
  bool initialized_{false};
 | 
			
		||||
  bool active_{false};
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
  std::unique_ptr<DNSServer> dns_server_{nullptr};
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern CaptivePortal *global_captive_portal;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
}  // namespace captive_portal
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ from esphome.const import (
 | 
			
		||||
    ICON_RADIATOR,
 | 
			
		||||
    ICON_RESTART,
 | 
			
		||||
    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_PARTS_PER_MILLION,
 | 
			
		||||
    UNIT_PARTS_PER_BILLION,
 | 
			
		||||
@@ -43,7 +43,7 @@ CONFIG_SCHEMA = (
 | 
			
		||||
                unit_of_measurement=UNIT_PARTS_PER_BILLION,
 | 
			
		||||
                icon=ICON_RADIATOR,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
                device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
 | 
			
		||||
 
 | 
			
		||||
@@ -140,9 +140,8 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
 | 
			
		||||
  /** Stop the cover.
 | 
			
		||||
   *
 | 
			
		||||
   * This is a legacy method and may be removed later, please use `.make_call()` instead.
 | 
			
		||||
   * As per solution from issue #2885 the call should include perform()
 | 
			
		||||
   */
 | 
			
		||||
  ESPDEPRECATED("stop() is deprecated, use make_call().set_command_stop().perform() instead.", "2021.9")
 | 
			
		||||
  ESPDEPRECATED("stop() is deprecated, use make_call().set_command_stop() instead.", "2021.9")
 | 
			
		||||
  void stop();
 | 
			
		||||
 | 
			
		||||
  void add_on_state_callback(std::function<void()> &&f);
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,9 @@ import requests
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
from esphome import git
 | 
			
		||||
from esphome.components.packages import validate_source_shorthand
 | 
			
		||||
from esphome.const import CONF_REF, CONF_WIFI, CONF_ESPHOME, CONF_PROJECT
 | 
			
		||||
from esphome.const import CONF_REF, CONF_WIFI
 | 
			
		||||
from esphome.wizard import wizard_file
 | 
			
		||||
from esphome.yaml_util import dump
 | 
			
		||||
 | 
			
		||||
@@ -53,17 +52,6 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    validate_full_url,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _final_validate(config):
 | 
			
		||||
    full_config = fv.full_config.get()[CONF_ESPHOME]
 | 
			
		||||
    if CONF_PROJECT not in full_config:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "Dashboard import requires the `esphome` -> `project` information to be provided."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = _final_validate
 | 
			
		||||
 | 
			
		||||
WIFI_CONFIG = """
 | 
			
		||||
 | 
			
		||||
wifi:
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.polling_component_schema("60s")),
 | 
			
		||||
    cv.only_on(["esp32", "esp8266"]),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/version.h"
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
@@ -14,7 +13,6 @@
 | 
			
		||||
 | 
			
		||||
#if ESP_IDF_VERSION_MAJOR >= 4
 | 
			
		||||
#include <esp32/rom/rtc.h>
 | 
			
		||||
#include <esp_chip_info.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <rom/rtc.h>
 | 
			
		||||
#endif
 | 
			
		||||
@@ -22,12 +20,8 @@
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <Esp.h>
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace debug {
 | 
			
		||||
@@ -39,8 +33,6 @@ static uint32_t get_free_heap() {
 | 
			
		||||
  return ESP.getFreeHeap();  // NOLINT(readability-static-accessed-through-instance)
 | 
			
		||||
#elif defined(USE_ESP32)
 | 
			
		||||
  return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
 | 
			
		||||
#elif defined(USE_RP2040)
 | 
			
		||||
  return rp2040.getFreeHeap();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -69,9 +61,9 @@ void DebugComponent::dump_config() {
 | 
			
		||||
  device_info += ESPHOME_VERSION;
 | 
			
		||||
 | 
			
		||||
  this->free_heap_ = get_free_heap();
 | 
			
		||||
  ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
 | 
			
		||||
  ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_);
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ARDUINO) && !defined(USE_RP2040)
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
  const char *flash_mode;
 | 
			
		||||
  switch (ESP.getFlashChipMode()) {  // NOLINT(readability-static-accessed-through-instance)
 | 
			
		||||
    case FM_QIO:
 | 
			
		||||
@@ -280,11 +272,6 @@ void DebugComponent::dump_config() {
 | 
			
		||||
  reset_reason = ESP.getResetReason().c_str();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  ESP_LOGD(TAG, "CPU Frequency: %u", rp2040.f_cpu());
 | 
			
		||||
  device_info += "CPU Frequency: " + to_string(rp2040.f_cpu());
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  if (this->device_info_ != nullptr) {
 | 
			
		||||
    if (device_info.length() > 255)
 | 
			
		||||
@@ -302,7 +289,7 @@ void DebugComponent::loop() {
 | 
			
		||||
  uint32_t new_free_heap = get_free_heap();
 | 
			
		||||
  if (new_free_heap < this->free_heap_ / 2) {
 | 
			
		||||
    this->free_heap_ = new_free_heap;
 | 
			
		||||
    ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
 | 
			
		||||
    ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_);
 | 
			
		||||
    this->status_momentary_warning("heap", 1000);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include <esp_sleep.h>
 | 
			
		||||
@@ -11,7 +11,6 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TIME
 | 
			
		||||
#include "esphome/components/time/real_time_clock.h"
 | 
			
		||||
#include "esphome/core/time.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -171,7 +170,7 @@ template<typename... Ts> class EnterDeepSleepAction : public Action<Ts...> {
 | 
			
		||||
      if (after_time)
 | 
			
		||||
        timestamp += 60 * 60 * 24;
 | 
			
		||||
 | 
			
		||||
      int32_t offset = ESPTime::timezone_offset();
 | 
			
		||||
      int32_t offset = time::ESPTime::timezone_offset();
 | 
			
		||||
      timestamp -= offset;  // Change timestamp to utc
 | 
			
		||||
      const uint32_t ms_left = (timestamp - timestamp_now) * 1000;
 | 
			
		||||
      this->deep_sleep_->set_sleep_duration(ms_left);
 | 
			
		||||
 
 | 
			
		||||
@@ -18,11 +18,10 @@ from esphome.core import coroutine_with_priority
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
display_ns = cg.esphome_ns.namespace("display")
 | 
			
		||||
Display = display_ns.class_("Display")
 | 
			
		||||
DisplayBuffer = display_ns.class_("DisplayBuffer")
 | 
			
		||||
DisplayPage = display_ns.class_("DisplayPage")
 | 
			
		||||
DisplayPagePtr = DisplayPage.operator("ptr")
 | 
			
		||||
DisplayRef = Display.operator("ref")
 | 
			
		||||
DisplayBufferRef = DisplayBuffer.operator("ref")
 | 
			
		||||
DisplayPageShowAction = display_ns.class_("DisplayPageShowAction", automation.Action)
 | 
			
		||||
DisplayPageShowNextAction = display_ns.class_(
 | 
			
		||||
    "DisplayPageShowNextAction", automation.Action
 | 
			
		||||
@@ -97,7 +96,7 @@ async def setup_display_core_(var, config):
 | 
			
		||||
        pages = []
 | 
			
		||||
        for conf in config[CONF_PAGES]:
 | 
			
		||||
            lambda_ = await cg.process_lambda(
 | 
			
		||||
                conf[CONF_LAMBDA], [(DisplayRef, "it")], return_type=cg.void
 | 
			
		||||
                conf[CONF_LAMBDA], [(DisplayBufferRef, "it")], return_type=cg.void
 | 
			
		||||
            )
 | 
			
		||||
            page = cg.new_Pvariable(conf[CONF_ID], lambda_)
 | 
			
		||||
            pages.append(page)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,343 +0,0 @@
 | 
			
		||||
#include "display.h"
 | 
			
		||||
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "display";
 | 
			
		||||
 | 
			
		||||
const Color COLOR_OFF(0, 0, 0, 0);
 | 
			
		||||
const Color COLOR_ON(255, 255, 255, 255);
 | 
			
		||||
 | 
			
		||||
void Display::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
 | 
			
		||||
void Display::clear() { this->fill(COLOR_OFF); }
 | 
			
		||||
void Display::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
 | 
			
		||||
void HOT Display::line(int x1, int y1, int x2, int y2, Color color) {
 | 
			
		||||
  const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
 | 
			
		||||
  const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
 | 
			
		||||
  int32_t err = dx + dy;
 | 
			
		||||
 | 
			
		||||
  while (true) {
 | 
			
		||||
    this->draw_pixel_at(x1, y1, color);
 | 
			
		||||
    if (x1 == x2 && y1 == y2)
 | 
			
		||||
      break;
 | 
			
		||||
    int32_t e2 = 2 * err;
 | 
			
		||||
    if (e2 >= dy) {
 | 
			
		||||
      err += dy;
 | 
			
		||||
      x1 += sx;
 | 
			
		||||
    }
 | 
			
		||||
    if (e2 <= dx) {
 | 
			
		||||
      err += dx;
 | 
			
		||||
      y1 += sy;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void HOT Display::horizontal_line(int x, int y, int width, Color color) {
 | 
			
		||||
  // Future: Could be made more efficient by manipulating buffer directly in certain rotations.
 | 
			
		||||
  for (int i = x; i < x + width; i++)
 | 
			
		||||
    this->draw_pixel_at(i, y, color);
 | 
			
		||||
}
 | 
			
		||||
void HOT Display::vertical_line(int x, int y, int height, Color color) {
 | 
			
		||||
  // Future: Could be made more efficient by manipulating buffer directly in certain rotations.
 | 
			
		||||
  for (int i = y; i < y + height; i++)
 | 
			
		||||
    this->draw_pixel_at(x, i, color);
 | 
			
		||||
}
 | 
			
		||||
void Display::rectangle(int x1, int y1, int width, int height, Color color) {
 | 
			
		||||
  this->horizontal_line(x1, y1, width, color);
 | 
			
		||||
  this->horizontal_line(x1, y1 + height - 1, width, color);
 | 
			
		||||
  this->vertical_line(x1, y1, height, color);
 | 
			
		||||
  this->vertical_line(x1 + width - 1, y1, height, color);
 | 
			
		||||
}
 | 
			
		||||
void Display::filled_rectangle(int x1, int y1, int width, int height, Color color) {
 | 
			
		||||
  // Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
 | 
			
		||||
  for (int i = y1; i < y1 + height; i++) {
 | 
			
		||||
    this->horizontal_line(x1, i, width, color);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void HOT Display::circle(int center_x, int center_xy, int radius, Color color) {
 | 
			
		||||
  int dx = -radius;
 | 
			
		||||
  int dy = 0;
 | 
			
		||||
  int err = 2 - 2 * radius;
 | 
			
		||||
  int e2;
 | 
			
		||||
 | 
			
		||||
  do {
 | 
			
		||||
    this->draw_pixel_at(center_x - dx, center_xy + dy, color);
 | 
			
		||||
    this->draw_pixel_at(center_x + dx, center_xy + dy, color);
 | 
			
		||||
    this->draw_pixel_at(center_x + dx, center_xy - dy, color);
 | 
			
		||||
    this->draw_pixel_at(center_x - dx, center_xy - dy, color);
 | 
			
		||||
    e2 = err;
 | 
			
		||||
    if (e2 < dy) {
 | 
			
		||||
      err += ++dy * 2 + 1;
 | 
			
		||||
      if (-dx == dy && e2 <= dx) {
 | 
			
		||||
        e2 = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (e2 > dx) {
 | 
			
		||||
      err += ++dx * 2 + 1;
 | 
			
		||||
    }
 | 
			
		||||
  } while (dx <= 0);
 | 
			
		||||
}
 | 
			
		||||
void Display::filled_circle(int center_x, int center_y, int radius, Color color) {
 | 
			
		||||
  int dx = -int32_t(radius);
 | 
			
		||||
  int dy = 0;
 | 
			
		||||
  int err = 2 - 2 * radius;
 | 
			
		||||
  int e2;
 | 
			
		||||
 | 
			
		||||
  do {
 | 
			
		||||
    this->draw_pixel_at(center_x - dx, center_y + dy, color);
 | 
			
		||||
    this->draw_pixel_at(center_x + dx, center_y + dy, color);
 | 
			
		||||
    this->draw_pixel_at(center_x + dx, center_y - dy, color);
 | 
			
		||||
    this->draw_pixel_at(center_x - dx, center_y - dy, color);
 | 
			
		||||
    int hline_width = 2 * (-dx) + 1;
 | 
			
		||||
    this->horizontal_line(center_x + dx, center_y + dy, hline_width, color);
 | 
			
		||||
    this->horizontal_line(center_x + dx, center_y - dy, hline_width, color);
 | 
			
		||||
    e2 = err;
 | 
			
		||||
    if (e2 < dy) {
 | 
			
		||||
      err += ++dy * 2 + 1;
 | 
			
		||||
      if (-dx == dy && e2 <= dx) {
 | 
			
		||||
        e2 = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (e2 > dx) {
 | 
			
		||||
      err += ++dx * 2 + 1;
 | 
			
		||||
    }
 | 
			
		||||
  } while (dx <= 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) {
 | 
			
		||||
  int x_start, y_start;
 | 
			
		||||
  int width, height;
 | 
			
		||||
  this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
 | 
			
		||||
  font->print(x_start, y_start, this, color, text);
 | 
			
		||||
}
 | 
			
		||||
void Display::vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg) {
 | 
			
		||||
  char buffer[256];
 | 
			
		||||
  int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
 | 
			
		||||
  if (ret > 0)
 | 
			
		||||
    this->print(x, y, font, color, align, buffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Display::image(int x, int y, BaseImage *image, Color color_on, Color color_off) {
 | 
			
		||||
  this->image(x, y, image, ImageAlign::TOP_LEFT, color_on, color_off);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Display::image(int x, int y, BaseImage *image, ImageAlign align, Color color_on, Color color_off) {
 | 
			
		||||
  auto x_align = ImageAlign(int(align) & (int(ImageAlign::HORIZONTAL_ALIGNMENT)));
 | 
			
		||||
  auto y_align = ImageAlign(int(align) & (int(ImageAlign::VERTICAL_ALIGNMENT)));
 | 
			
		||||
 | 
			
		||||
  switch (x_align) {
 | 
			
		||||
    case ImageAlign::RIGHT:
 | 
			
		||||
      x -= image->get_width();
 | 
			
		||||
      break;
 | 
			
		||||
    case ImageAlign::CENTER_HORIZONTAL:
 | 
			
		||||
      x -= image->get_width() / 2;
 | 
			
		||||
      break;
 | 
			
		||||
    case ImageAlign::LEFT:
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (y_align) {
 | 
			
		||||
    case ImageAlign::BOTTOM:
 | 
			
		||||
      y -= image->get_height();
 | 
			
		||||
      break;
 | 
			
		||||
    case ImageAlign::CENTER_VERTICAL:
 | 
			
		||||
      y -= image->get_height() / 2;
 | 
			
		||||
      break;
 | 
			
		||||
    case ImageAlign::TOP:
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  image->draw(x, y, this, color_on, color_off);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_GRAPH
 | 
			
		||||
void Display::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); }
 | 
			
		||||
void Display::legend(int x, int y, graph::Graph *graph, Color color_on) { graph->draw_legend(this, x, y, color_on); }
 | 
			
		||||
#endif  // USE_GRAPH
 | 
			
		||||
 | 
			
		||||
#ifdef USE_QR_CODE
 | 
			
		||||
void Display::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) {
 | 
			
		||||
  qr_code->draw(this, x, y, color_on, scale);
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_QR_CODE
 | 
			
		||||
 | 
			
		||||
void Display::get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1,
 | 
			
		||||
                              int *width, int *height) {
 | 
			
		||||
  int x_offset, baseline;
 | 
			
		||||
  font->measure(text, width, &x_offset, &baseline, height);
 | 
			
		||||
 | 
			
		||||
  auto x_align = TextAlign(int(align) & 0x18);
 | 
			
		||||
  auto y_align = TextAlign(int(align) & 0x07);
 | 
			
		||||
 | 
			
		||||
  switch (x_align) {
 | 
			
		||||
    case TextAlign::RIGHT:
 | 
			
		||||
      *x1 = x - *width;
 | 
			
		||||
      break;
 | 
			
		||||
    case TextAlign::CENTER_HORIZONTAL:
 | 
			
		||||
      *x1 = x - (*width) / 2;
 | 
			
		||||
      break;
 | 
			
		||||
    case TextAlign::LEFT:
 | 
			
		||||
    default:
 | 
			
		||||
      // LEFT
 | 
			
		||||
      *x1 = x;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (y_align) {
 | 
			
		||||
    case TextAlign::BOTTOM:
 | 
			
		||||
      *y1 = y - *height;
 | 
			
		||||
      break;
 | 
			
		||||
    case TextAlign::BASELINE:
 | 
			
		||||
      *y1 = y - baseline;
 | 
			
		||||
      break;
 | 
			
		||||
    case TextAlign::CENTER_VERTICAL:
 | 
			
		||||
      *y1 = y - (*height) / 2;
 | 
			
		||||
      break;
 | 
			
		||||
    case TextAlign::TOP:
 | 
			
		||||
    default:
 | 
			
		||||
      *y1 = y;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void Display::print(int x, int y, BaseFont *font, Color color, const char *text) {
 | 
			
		||||
  this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
 | 
			
		||||
}
 | 
			
		||||
void Display::print(int x, int y, BaseFont *font, TextAlign align, const char *text) {
 | 
			
		||||
  this->print(x, y, font, COLOR_ON, align, text);
 | 
			
		||||
}
 | 
			
		||||
void Display::print(int x, int y, BaseFont *font, const char *text) {
 | 
			
		||||
  this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
 | 
			
		||||
}
 | 
			
		||||
void Display::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) {
 | 
			
		||||
  va_list arg;
 | 
			
		||||
  va_start(arg, format);
 | 
			
		||||
  this->vprintf_(x, y, font, color, align, format, arg);
 | 
			
		||||
  va_end(arg);
 | 
			
		||||
}
 | 
			
		||||
void Display::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) {
 | 
			
		||||
  va_list arg;
 | 
			
		||||
  va_start(arg, format);
 | 
			
		||||
  this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
 | 
			
		||||
  va_end(arg);
 | 
			
		||||
}
 | 
			
		||||
void Display::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) {
 | 
			
		||||
  va_list arg;
 | 
			
		||||
  va_start(arg, format);
 | 
			
		||||
  this->vprintf_(x, y, font, COLOR_ON, align, format, arg);
 | 
			
		||||
  va_end(arg);
 | 
			
		||||
}
 | 
			
		||||
void Display::printf(int x, int y, BaseFont *font, const char *format, ...) {
 | 
			
		||||
  va_list arg;
 | 
			
		||||
  va_start(arg, format);
 | 
			
		||||
  this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg);
 | 
			
		||||
  va_end(arg);
 | 
			
		||||
}
 | 
			
		||||
void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
 | 
			
		||||
void Display::set_pages(std::vector<DisplayPage *> pages) {
 | 
			
		||||
  for (auto *page : pages)
 | 
			
		||||
    page->set_parent(this);
 | 
			
		||||
 | 
			
		||||
  for (uint32_t i = 0; i < pages.size() - 1; i++) {
 | 
			
		||||
    pages[i]->set_next(pages[i + 1]);
 | 
			
		||||
    pages[i + 1]->set_prev(pages[i]);
 | 
			
		||||
  }
 | 
			
		||||
  pages[0]->set_prev(pages[pages.size() - 1]);
 | 
			
		||||
  pages[pages.size() - 1]->set_next(pages[0]);
 | 
			
		||||
  this->show_page(pages[0]);
 | 
			
		||||
}
 | 
			
		||||
void Display::show_page(DisplayPage *page) {
 | 
			
		||||
  this->previous_page_ = this->page_;
 | 
			
		||||
  this->page_ = page;
 | 
			
		||||
  if (this->previous_page_ != this->page_) {
 | 
			
		||||
    for (auto *t : on_page_change_triggers_)
 | 
			
		||||
      t->process(this->previous_page_, this->page_);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void Display::show_next_page() { this->page_->show_next(); }
 | 
			
		||||
void Display::show_prev_page() { this->page_->show_prev(); }
 | 
			
		||||
void Display::do_update_() {
 | 
			
		||||
  if (this->auto_clear_enabled_) {
 | 
			
		||||
    this->clear();
 | 
			
		||||
  }
 | 
			
		||||
  if (this->page_ != nullptr) {
 | 
			
		||||
    this->page_->get_writer()(*this);
 | 
			
		||||
  } else if (this->writer_.has_value()) {
 | 
			
		||||
    (*this->writer_)(*this);
 | 
			
		||||
  }
 | 
			
		||||
  // remove all not ended clipping regions
 | 
			
		||||
  while (is_clipping()) {
 | 
			
		||||
    end_clipping();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
 | 
			
		||||
  if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
 | 
			
		||||
    this->trigger(from, to);
 | 
			
		||||
}
 | 
			
		||||
void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  size_t ret = time.strftime(buffer, sizeof(buffer), format);
 | 
			
		||||
  if (ret > 0)
 | 
			
		||||
    this->print(x, y, font, color, align, buffer);
 | 
			
		||||
}
 | 
			
		||||
void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) {
 | 
			
		||||
  this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
 | 
			
		||||
}
 | 
			
		||||
void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) {
 | 
			
		||||
  this->strftime(x, y, font, COLOR_ON, align, format, time);
 | 
			
		||||
}
 | 
			
		||||
void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) {
 | 
			
		||||
  this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Display::start_clipping(Rect rect) {
 | 
			
		||||
  if (!this->clipping_rectangle_.empty()) {
 | 
			
		||||
    Rect r = this->clipping_rectangle_.back();
 | 
			
		||||
    rect.shrink(r);
 | 
			
		||||
  }
 | 
			
		||||
  this->clipping_rectangle_.push_back(rect);
 | 
			
		||||
}
 | 
			
		||||
void Display::end_clipping() {
 | 
			
		||||
  if (this->clipping_rectangle_.empty()) {
 | 
			
		||||
    ESP_LOGE(TAG, "clear: Clipping is not set.");
 | 
			
		||||
  } else {
 | 
			
		||||
    this->clipping_rectangle_.pop_back();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void Display::extend_clipping(Rect add_rect) {
 | 
			
		||||
  if (this->clipping_rectangle_.empty()) {
 | 
			
		||||
    ESP_LOGE(TAG, "add: Clipping is not set.");
 | 
			
		||||
  } else {
 | 
			
		||||
    this->clipping_rectangle_.back().extend(add_rect);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void Display::shrink_clipping(Rect add_rect) {
 | 
			
		||||
  if (this->clipping_rectangle_.empty()) {
 | 
			
		||||
    ESP_LOGE(TAG, "add: Clipping is not set.");
 | 
			
		||||
  } else {
 | 
			
		||||
    this->clipping_rectangle_.back().shrink(add_rect);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
Rect Display::get_clipping() {
 | 
			
		||||
  if (this->clipping_rectangle_.empty()) {
 | 
			
		||||
    return Rect();
 | 
			
		||||
  } else {
 | 
			
		||||
    return this->clipping_rectangle_.back();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
 | 
			
		||||
void DisplayPage::show() { this->parent_->show_page(this); }
 | 
			
		||||
void DisplayPage::show_next() { this->next_->show(); }
 | 
			
		||||
void DisplayPage::show_prev() { this->prev_->show(); }
 | 
			
		||||
void DisplayPage::set_parent(Display *parent) { this->parent_ = parent; }
 | 
			
		||||
void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
 | 
			
		||||
void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
 | 
			
		||||
const display_writer_t &DisplayPage::get_writer() const { return this->writer_; }
 | 
			
		||||
 | 
			
		||||
}  // namespace display
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,566 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <cstdarg>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "rect.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/color.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/time.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_GRAPH
 | 
			
		||||
#include "esphome/components/graph/graph.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_QR_CODE
 | 
			
		||||
#include "esphome/components/qr_code/qr_code.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display {
 | 
			
		||||
 | 
			
		||||
/** TextAlign is used to tell the display class how to position a piece of text. By default
 | 
			
		||||
 * the coordinates you enter for the print*() functions take the upper left corner of the text
 | 
			
		||||
 * as the "anchor" point. You can customize this behavior to, for example, make the coordinates
 | 
			
		||||
 * refer to the *center* of the text.
 | 
			
		||||
 *
 | 
			
		||||
 * All text alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
 | 
			
		||||
 * these options are allowed:
 | 
			
		||||
 *
 | 
			
		||||
 * - LEFT (x-coordinate of anchor point is on left)
 | 
			
		||||
 * - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the text)
 | 
			
		||||
 * - RIGHT (x-coordinate of anchor point is on right)
 | 
			
		||||
 *
 | 
			
		||||
 * For the Y-Axis alignment these options are allowed:
 | 
			
		||||
 *
 | 
			
		||||
 * - TOP (y-coordinate of anchor is on the top of the text)
 | 
			
		||||
 * - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the text)
 | 
			
		||||
 * - BASELINE (y-coordinate of anchor is on the baseline of the text)
 | 
			
		||||
 * - BOTTOM (y-coordinate of anchor is on the bottom of the text)
 | 
			
		||||
 *
 | 
			
		||||
 * These options are then combined to create combined TextAlignment options like:
 | 
			
		||||
 * - TOP_LEFT (default)
 | 
			
		||||
 * - CENTER (anchor point is in the middle of the text bounds)
 | 
			
		||||
 * - ...
 | 
			
		||||
 */
 | 
			
		||||
enum class TextAlign {
 | 
			
		||||
  TOP = 0x00,
 | 
			
		||||
  CENTER_VERTICAL = 0x01,
 | 
			
		||||
  BASELINE = 0x02,
 | 
			
		||||
  BOTTOM = 0x04,
 | 
			
		||||
 | 
			
		||||
  LEFT = 0x00,
 | 
			
		||||
  CENTER_HORIZONTAL = 0x08,
 | 
			
		||||
  RIGHT = 0x10,
 | 
			
		||||
 | 
			
		||||
  TOP_LEFT = TOP | LEFT,
 | 
			
		||||
  TOP_CENTER = TOP | CENTER_HORIZONTAL,
 | 
			
		||||
  TOP_RIGHT = TOP | RIGHT,
 | 
			
		||||
 | 
			
		||||
  CENTER_LEFT = CENTER_VERTICAL | LEFT,
 | 
			
		||||
  CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
 | 
			
		||||
  CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
 | 
			
		||||
 | 
			
		||||
  BASELINE_LEFT = BASELINE | LEFT,
 | 
			
		||||
  BASELINE_CENTER = BASELINE | CENTER_HORIZONTAL,
 | 
			
		||||
  BASELINE_RIGHT = BASELINE | RIGHT,
 | 
			
		||||
 | 
			
		||||
  BOTTOM_LEFT = BOTTOM | LEFT,
 | 
			
		||||
  BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
 | 
			
		||||
  BOTTOM_RIGHT = BOTTOM | RIGHT,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** ImageAlign is used to tell the display class how to position a image. By default
 | 
			
		||||
 * the coordinates you enter for the image() functions take the upper left corner of the image
 | 
			
		||||
 * as the "anchor" point. You can customize this behavior to, for example, make the coordinates
 | 
			
		||||
 * refer to the *center* of the image.
 | 
			
		||||
 *
 | 
			
		||||
 * All image alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
 | 
			
		||||
 * these options are allowed:
 | 
			
		||||
 *
 | 
			
		||||
 * - LEFT (x-coordinate of anchor point is on left)
 | 
			
		||||
 * - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the image)
 | 
			
		||||
 * - RIGHT (x-coordinate of anchor point is on right)
 | 
			
		||||
 *
 | 
			
		||||
 * For the Y-Axis alignment these options are allowed:
 | 
			
		||||
 *
 | 
			
		||||
 * - TOP (y-coordinate of anchor is on the top of the image)
 | 
			
		||||
 * - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the image)
 | 
			
		||||
 * - BOTTOM (y-coordinate of anchor is on the bottom of the image)
 | 
			
		||||
 *
 | 
			
		||||
 * These options are then combined to create combined TextAlignment options like:
 | 
			
		||||
 * - TOP_LEFT (default)
 | 
			
		||||
 * - CENTER (anchor point is in the middle of the image bounds)
 | 
			
		||||
 * - ...
 | 
			
		||||
 */
 | 
			
		||||
enum class ImageAlign {
 | 
			
		||||
  TOP = 0x00,
 | 
			
		||||
  CENTER_VERTICAL = 0x01,
 | 
			
		||||
  BOTTOM = 0x02,
 | 
			
		||||
 | 
			
		||||
  LEFT = 0x00,
 | 
			
		||||
  CENTER_HORIZONTAL = 0x04,
 | 
			
		||||
  RIGHT = 0x08,
 | 
			
		||||
 | 
			
		||||
  TOP_LEFT = TOP | LEFT,
 | 
			
		||||
  TOP_CENTER = TOP | CENTER_HORIZONTAL,
 | 
			
		||||
  TOP_RIGHT = TOP | RIGHT,
 | 
			
		||||
 | 
			
		||||
  CENTER_LEFT = CENTER_VERTICAL | LEFT,
 | 
			
		||||
  CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
 | 
			
		||||
  CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
 | 
			
		||||
 | 
			
		||||
  BOTTOM_LEFT = BOTTOM | LEFT,
 | 
			
		||||
  BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
 | 
			
		||||
  BOTTOM_RIGHT = BOTTOM | RIGHT,
 | 
			
		||||
 | 
			
		||||
  HORIZONTAL_ALIGNMENT = LEFT | CENTER_HORIZONTAL | RIGHT,
 | 
			
		||||
  VERTICAL_ALIGNMENT = TOP | CENTER_VERTICAL | BOTTOM
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum DisplayType {
 | 
			
		||||
  DISPLAY_TYPE_BINARY = 1,
 | 
			
		||||
  DISPLAY_TYPE_GRAYSCALE = 2,
 | 
			
		||||
  DISPLAY_TYPE_COLOR = 3,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum DisplayRotation {
 | 
			
		||||
  DISPLAY_ROTATION_0_DEGREES = 0,
 | 
			
		||||
  DISPLAY_ROTATION_90_DEGREES = 90,
 | 
			
		||||
  DISPLAY_ROTATION_180_DEGREES = 180,
 | 
			
		||||
  DISPLAY_ROTATION_270_DEGREES = 270,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Display;
 | 
			
		||||
class DisplayPage;
 | 
			
		||||
class DisplayOnPageChangeTrigger;
 | 
			
		||||
 | 
			
		||||
using display_writer_t = std::function<void(Display &)>;
 | 
			
		||||
 | 
			
		||||
#define LOG_DISPLAY(prefix, type, obj) \
 | 
			
		||||
  if ((obj) != nullptr) { \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, prefix type); \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s  Rotations: %d °", prefix, (obj)->rotation_); \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s  Dimensions: %dpx x %dpx", prefix, (obj)->get_width(), (obj)->get_height()); \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
/// Turn the pixel OFF.
 | 
			
		||||
extern const Color COLOR_OFF;
 | 
			
		||||
/// Turn the pixel ON.
 | 
			
		||||
extern const Color COLOR_ON;
 | 
			
		||||
 | 
			
		||||
class BaseImage {
 | 
			
		||||
 public:
 | 
			
		||||
  virtual void draw(int x, int y, Display *display, Color color_on, Color color_off) = 0;
 | 
			
		||||
  virtual int get_width() const = 0;
 | 
			
		||||
  virtual int get_height() const = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BaseFont {
 | 
			
		||||
 public:
 | 
			
		||||
  virtual void print(int x, int y, Display *display, Color color, const char *text) = 0;
 | 
			
		||||
  virtual void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Display {
 | 
			
		||||
 public:
 | 
			
		||||
  /// Fill the entire screen with the given color.
 | 
			
		||||
  virtual void fill(Color color);
 | 
			
		||||
  /// Clear the entire screen by filling it with OFF pixels.
 | 
			
		||||
  void clear();
 | 
			
		||||
 | 
			
		||||
  /// Get the width of the image in pixels with rotation applied.
 | 
			
		||||
  virtual int get_width() = 0;
 | 
			
		||||
  /// Get the height of the image in pixels with rotation applied.
 | 
			
		||||
  virtual int get_height() = 0;
 | 
			
		||||
 | 
			
		||||
  /// Set a single pixel at the specified coordinates to default color.
 | 
			
		||||
  inline void draw_pixel_at(int x, int y) { this->draw_pixel_at(x, y, COLOR_ON); }
 | 
			
		||||
 | 
			
		||||
  /// Set a single pixel at the specified coordinates to the given color.
 | 
			
		||||
  virtual void draw_pixel_at(int x, int y, Color color) = 0;
 | 
			
		||||
 | 
			
		||||
  /// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
 | 
			
		||||
  void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
 | 
			
		||||
  void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /// Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
 | 
			
		||||
  void vertical_line(int x, int y, int height, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at
 | 
			
		||||
  /// [x1+width,y1+height].
 | 
			
		||||
  void rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height].
 | 
			
		||||
  void filled_rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color.
 | 
			
		||||
  void circle(int center_x, int center_xy, int radius, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
 | 
			
		||||
  void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /** Print `text` with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param y The y coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param color The color to draw the text with.
 | 
			
		||||
   * @param align The alignment of the text.
 | 
			
		||||
   * @param text The text to draw.
 | 
			
		||||
   */
 | 
			
		||||
  void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text);
 | 
			
		||||
 | 
			
		||||
  /** Print `text` with the top left at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param color The color to draw the text with.
 | 
			
		||||
   * @param text The text to draw.
 | 
			
		||||
   */
 | 
			
		||||
  void print(int x, int y, BaseFont *font, Color color, const char *text);
 | 
			
		||||
 | 
			
		||||
  /** Print `text` with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param y The y coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param align The alignment of the text.
 | 
			
		||||
   * @param text The text to draw.
 | 
			
		||||
   */
 | 
			
		||||
  void print(int x, int y, BaseFont *font, TextAlign align, const char *text);
 | 
			
		||||
 | 
			
		||||
  /** Print `text` with the top left at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param text The text to draw.
 | 
			
		||||
   */
 | 
			
		||||
  void print(int x, int y, BaseFont *font, const char *text);
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param y The y coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param color The color to draw the text with.
 | 
			
		||||
   * @param align The alignment of the text.
 | 
			
		||||
   * @param format The format to use.
 | 
			
		||||
   * @param ... The arguments to use for the text formatting.
 | 
			
		||||
   */
 | 
			
		||||
  void printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...)
 | 
			
		||||
      __attribute__((format(printf, 7, 8)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param color The color to draw the text with.
 | 
			
		||||
   * @param format The format to use.
 | 
			
		||||
   * @param ... The arguments to use for the text formatting.
 | 
			
		||||
   */
 | 
			
		||||
  void printf(int x, int y, BaseFont *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param y The y coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param align The alignment of the text.
 | 
			
		||||
   * @param format The format to use.
 | 
			
		||||
   * @param ... The arguments to use for the text formatting.
 | 
			
		||||
   */
 | 
			
		||||
  void printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...)
 | 
			
		||||
      __attribute__((format(printf, 6, 7)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param format The format to use.
 | 
			
		||||
   * @param ... The arguments to use for the text formatting.
 | 
			
		||||
   */
 | 
			
		||||
  void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param y The y coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param color The color to draw the text with.
 | 
			
		||||
   * @param align The alignment of the text.
 | 
			
		||||
   * @param format The strftime format to use.
 | 
			
		||||
   * @param time The time to format.
 | 
			
		||||
   */
 | 
			
		||||
  void strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time)
 | 
			
		||||
      __attribute__((format(strftime, 7, 0)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param color The color to draw the text with.
 | 
			
		||||
   * @param format The strftime format to use.
 | 
			
		||||
   * @param time The time to format.
 | 
			
		||||
   */
 | 
			
		||||
  void strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time)
 | 
			
		||||
      __attribute__((format(strftime, 6, 0)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param y The y coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param align The alignment of the text.
 | 
			
		||||
   * @param format The strftime format to use.
 | 
			
		||||
   * @param time The time to format.
 | 
			
		||||
   */
 | 
			
		||||
  void strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time)
 | 
			
		||||
      __attribute__((format(strftime, 6, 0)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param format The strftime format to use.
 | 
			
		||||
   * @param time The time to format.
 | 
			
		||||
   */
 | 
			
		||||
  void strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) __attribute__((format(strftime, 5, 0)));
 | 
			
		||||
 | 
			
		||||
  /** Draw the `image` with the top-left corner at [x,y] to the screen.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param image The image to draw.
 | 
			
		||||
   * @param color_on The color to replace in binary images for the on bits.
 | 
			
		||||
   * @param color_off The color to replace in binary images for the off bits.
 | 
			
		||||
   */
 | 
			
		||||
  void image(int x, int y, BaseImage *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
 | 
			
		||||
 | 
			
		||||
  /** Draw the `image` at [x,y] to the screen.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param image The image to draw.
 | 
			
		||||
   * @param align The alignment of the image.
 | 
			
		||||
   * @param color_on The color to replace in binary images for the on bits.
 | 
			
		||||
   * @param color_off The color to replace in binary images for the off bits.
 | 
			
		||||
   */
 | 
			
		||||
  void image(int x, int y, BaseImage *image, ImageAlign align, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
 | 
			
		||||
 | 
			
		||||
#ifdef USE_GRAPH
 | 
			
		||||
  /** Draw the `graph` with the top-left corner at [x,y] to the screen.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param graph The graph id to draw
 | 
			
		||||
   * @param color_on The color to replace in binary images for the on bits.
 | 
			
		||||
   */
 | 
			
		||||
  void graph(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /** Draw the `legend` for graph with the top-left corner at [x,y] to the screen.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param graph The graph id for which the legend applies to
 | 
			
		||||
   * @param graph The graph id for which the legend applies to
 | 
			
		||||
   * @param graph The graph id for which the legend applies to
 | 
			
		||||
   * @param name_font The font used for the trace name
 | 
			
		||||
   * @param value_font The font used for the trace value and units
 | 
			
		||||
   * @param color_on The color of the border
 | 
			
		||||
   */
 | 
			
		||||
  void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
 | 
			
		||||
#endif  // USE_GRAPH
 | 
			
		||||
 | 
			
		||||
#ifdef USE_QR_CODE
 | 
			
		||||
  /** Draw the `qr_code` with the top-left corner at [x,y] to the screen.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param qr_code The qr_code to draw
 | 
			
		||||
   * @param color_on The color to replace in binary images for the on bits.
 | 
			
		||||
   */
 | 
			
		||||
  void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  /** Get the text bounds of the given string.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate to place the string at, can be 0 if only interested in dimensions.
 | 
			
		||||
   * @param y The y coordinate to place the string at, can be 0 if only interested in dimensions.
 | 
			
		||||
   * @param text The text to measure.
 | 
			
		||||
   * @param font The font to measure the text bounds with.
 | 
			
		||||
   * @param align The alignment of the text. Set to TextAlign::TOP_LEFT if only interested in dimensions.
 | 
			
		||||
   * @param x1 A pointer to store the returned x coordinate of the upper left corner in.
 | 
			
		||||
   * @param y1 A pointer to store the returned y coordinate of the upper left corner in.
 | 
			
		||||
   * @param width A pointer to store the returned text width in.
 | 
			
		||||
   * @param height A pointer to store the returned text height in.
 | 
			
		||||
   */
 | 
			
		||||
  void get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, int *width,
 | 
			
		||||
                       int *height);
 | 
			
		||||
 | 
			
		||||
  /// Internal method to set the display writer lambda.
 | 
			
		||||
  void set_writer(display_writer_t &&writer);
 | 
			
		||||
 | 
			
		||||
  void show_page(DisplayPage *page);
 | 
			
		||||
  void show_next_page();
 | 
			
		||||
  void show_prev_page();
 | 
			
		||||
 | 
			
		||||
  void set_pages(std::vector<DisplayPage *> pages);
 | 
			
		||||
 | 
			
		||||
  const DisplayPage *get_active_page() const { return this->page_; }
 | 
			
		||||
 | 
			
		||||
  void add_on_page_change_trigger(DisplayOnPageChangeTrigger *t) { this->on_page_change_triggers_.push_back(t); }
 | 
			
		||||
 | 
			
		||||
  /// Internal method to set the display rotation with.
 | 
			
		||||
  void set_rotation(DisplayRotation rotation);
 | 
			
		||||
 | 
			
		||||
  // Internal method to set display auto clearing.
 | 
			
		||||
  void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
 | 
			
		||||
 | 
			
		||||
  DisplayRotation get_rotation() const { return this->rotation_; }
 | 
			
		||||
 | 
			
		||||
  /** Get the type of display that the buffer corresponds to. In case of dynamically configurable displays,
 | 
			
		||||
   * returns the type the display is currently configured to.
 | 
			
		||||
   */
 | 
			
		||||
  virtual DisplayType get_display_type() = 0;
 | 
			
		||||
 | 
			
		||||
  /** Set the clipping rectangle for further drawing
 | 
			
		||||
   *
 | 
			
		||||
   * @param[in]  rect:       Pointer to Rect for clipping (or NULL for entire screen)
 | 
			
		||||
   *
 | 
			
		||||
   * return true if success, false if error
 | 
			
		||||
   */
 | 
			
		||||
  void start_clipping(Rect rect);
 | 
			
		||||
  void start_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
 | 
			
		||||
    start_clipping(Rect(left, top, right - left, bottom - top));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /** Add a rectangular region to the invalidation region
 | 
			
		||||
   * - This is usually called when an element has been modified
 | 
			
		||||
   *
 | 
			
		||||
   * @param[in]  rect: Rectangle to add to the invalidation region
 | 
			
		||||
   */
 | 
			
		||||
  void extend_clipping(Rect rect);
 | 
			
		||||
  void extend_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
 | 
			
		||||
    this->extend_clipping(Rect(left, top, right - left, bottom - top));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /** substract a rectangular region to the invalidation region
 | 
			
		||||
   *  - This is usually called when an element has been modified
 | 
			
		||||
   *
 | 
			
		||||
   * @param[in]  rect: Rectangle to add to the invalidation region
 | 
			
		||||
   */
 | 
			
		||||
  void shrink_clipping(Rect rect);
 | 
			
		||||
  void shrink_clipping(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
 | 
			
		||||
    this->shrink_clipping(Rect(left, top, right - left, bottom - top));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /** Reset the invalidation region
 | 
			
		||||
   */
 | 
			
		||||
  void end_clipping();
 | 
			
		||||
 | 
			
		||||
  /** Get the current the clipping rectangle
 | 
			
		||||
   *
 | 
			
		||||
   * return rect for active clipping region
 | 
			
		||||
   */
 | 
			
		||||
  Rect get_clipping();
 | 
			
		||||
 | 
			
		||||
  bool is_clipping() const { return !this->clipping_rectangle_.empty(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg);
 | 
			
		||||
 | 
			
		||||
  void do_update_();
 | 
			
		||||
 | 
			
		||||
  DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
 | 
			
		||||
  optional<display_writer_t> writer_{};
 | 
			
		||||
  DisplayPage *page_{nullptr};
 | 
			
		||||
  DisplayPage *previous_page_{nullptr};
 | 
			
		||||
  std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
 | 
			
		||||
  bool auto_clear_enabled_{true};
 | 
			
		||||
  std::vector<Rect> clipping_rectangle_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayPage {
 | 
			
		||||
 public:
 | 
			
		||||
  DisplayPage(display_writer_t writer);
 | 
			
		||||
  void show();
 | 
			
		||||
  void show_next();
 | 
			
		||||
  void show_prev();
 | 
			
		||||
  void set_parent(Display *parent);
 | 
			
		||||
  void set_prev(DisplayPage *prev);
 | 
			
		||||
  void set_next(DisplayPage *next);
 | 
			
		||||
  const display_writer_t &get_writer() const;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Display *parent_;
 | 
			
		||||
  display_writer_t writer_;
 | 
			
		||||
  DisplayPage *prev_{nullptr};
 | 
			
		||||
  DisplayPage *next_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  TEMPLATABLE_VALUE(DisplayPage *, page)
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    auto *page = this->page_.value(x...);
 | 
			
		||||
    if (page != nullptr) {
 | 
			
		||||
      page->show();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class DisplayPageShowNextAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  DisplayPageShowNextAction(Display *buffer) : buffer_(buffer) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->buffer_->show_next_page(); }
 | 
			
		||||
 | 
			
		||||
  Display *buffer_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class DisplayPageShowPrevAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  DisplayPageShowPrevAction(Display *buffer) : buffer_(buffer) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->buffer_->show_prev_page(); }
 | 
			
		||||
 | 
			
		||||
  Display *buffer_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class DisplayIsDisplayingPageCondition : public Condition<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  DisplayIsDisplayingPageCondition(Display *parent) : parent_(parent) {}
 | 
			
		||||
 | 
			
		||||
  void set_page(DisplayPage *page) { this->page_ = page; }
 | 
			
		||||
  bool check(Ts... x) override { return this->parent_->get_active_page() == this->page_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Display *parent_;
 | 
			
		||||
  DisplayPage *page_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayOnPageChangeTrigger : public Trigger<DisplayPage *, DisplayPage *> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DisplayOnPageChangeTrigger(Display *parent) { parent->add_on_page_change_trigger(this); }
 | 
			
		||||
  void process(DisplayPage *from, DisplayPage *to);
 | 
			
		||||
  void set_from(DisplayPage *p) { this->from_ = p; }
 | 
			
		||||
  void set_to(DisplayPage *p) { this->to_ = p; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayPage *from_{nullptr};
 | 
			
		||||
  DisplayPage *to_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace display
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,15 +1,107 @@
 | 
			
		||||
#include "display_buffer.h"
 | 
			
		||||
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/color.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "display";
 | 
			
		||||
 | 
			
		||||
const Color COLOR_OFF(0, 0, 0, 0);
 | 
			
		||||
const Color COLOR_ON(255, 255, 255, 255);
 | 
			
		||||
 | 
			
		||||
void Rect::expand(int16_t horizontal, int16_t vertical) {
 | 
			
		||||
  if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) {
 | 
			
		||||
    this->x = this->x - horizontal;
 | 
			
		||||
    this->y = this->y - vertical;
 | 
			
		||||
    this->w = this->w + (2 * horizontal);
 | 
			
		||||
    this->h = this->h + (2 * vertical);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Rect::extend(Rect rect) {
 | 
			
		||||
  if (!this->is_set()) {
 | 
			
		||||
    this->x = rect.x;
 | 
			
		||||
    this->y = rect.y;
 | 
			
		||||
    this->w = rect.w;
 | 
			
		||||
    this->h = rect.h;
 | 
			
		||||
  } else {
 | 
			
		||||
    if (this->x > rect.x) {
 | 
			
		||||
      this->w = this->w + (this->x - rect.x);
 | 
			
		||||
      this->x = rect.x;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->y > rect.y) {
 | 
			
		||||
      this->h = this->h + (this->y - rect.y);
 | 
			
		||||
      this->y = rect.y;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->x2() < rect.x2()) {
 | 
			
		||||
      this->w = rect.x2() - this->x;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->y2() < rect.y2()) {
 | 
			
		||||
      this->h = rect.y2() - this->y;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void Rect::shrink(Rect rect) {
 | 
			
		||||
  if (!this->inside(rect)) {
 | 
			
		||||
    (*this) = Rect();
 | 
			
		||||
  } else {
 | 
			
		||||
    if (this->x2() > rect.x2()) {
 | 
			
		||||
      this->w = rect.x2() - this->x;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->x < rect.x) {
 | 
			
		||||
      this->w = this->w + (this->x - rect.x);
 | 
			
		||||
      this->x = rect.x;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->y2() > rect.y2()) {
 | 
			
		||||
      this->h = rect.y2() - this->y;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->y < rect.y) {
 | 
			
		||||
      this->h = this->h + (this->y - rect.y);
 | 
			
		||||
      this->y = rect.y;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Rect::equal(Rect rect) {
 | 
			
		||||
  return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) {  // NOLINT
 | 
			
		||||
  if (!this->is_set()) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  if (absolute) {
 | 
			
		||||
    return ((test_x >= this->x) && (test_x <= this->x2()) && (test_y >= this->y) && (test_y <= this->y2()));
 | 
			
		||||
  } else {
 | 
			
		||||
    return ((test_x >= 0) && (test_x <= this->w) && (test_y >= 0) && (test_y <= this->h));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Rect::inside(Rect rect, bool absolute) {
 | 
			
		||||
  if (!this->is_set() || !rect.is_set()) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  if (absolute) {
 | 
			
		||||
    return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y));
 | 
			
		||||
  } else {
 | 
			
		||||
    return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Rect::info(const std::string &prefix) {
 | 
			
		||||
  if (this->is_set()) {
 | 
			
		||||
    ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d] (%3d,%3d)", prefix.c_str(), this->x, this->y, this->w, this->h, this->x2(),
 | 
			
		||||
             this->y2());
 | 
			
		||||
  } else
 | 
			
		||||
    ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
 | 
			
		||||
  ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
 | 
			
		||||
  this->buffer_ = allocator.allocate(buffer_length);
 | 
			
		||||
@@ -20,6 +112,8 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) {
 | 
			
		||||
  this->clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
 | 
			
		||||
void DisplayBuffer::clear() { this->fill(COLOR_OFF); }
 | 
			
		||||
int DisplayBuffer::get_width() {
 | 
			
		||||
  switch (this->rotation_) {
 | 
			
		||||
    case DISPLAY_ROTATION_90_DEGREES:
 | 
			
		||||
@@ -31,7 +125,6 @@ int DisplayBuffer::get_width() {
 | 
			
		||||
      return this->get_width_internal();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int DisplayBuffer::get_height() {
 | 
			
		||||
  switch (this->rotation_) {
 | 
			
		||||
    case DISPLAY_ROTATION_0_DEGREES:
 | 
			
		||||
@@ -43,7 +136,7 @@ int DisplayBuffer::get_height() {
 | 
			
		||||
      return this->get_width_internal();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
 | 
			
		||||
void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
 | 
			
		||||
  if (!this->get_clipping().inside(x, y))
 | 
			
		||||
    return;  // NOLINT
 | 
			
		||||
@@ -67,6 +160,588 @@ void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
 | 
			
		||||
  this->draw_absolute_pixel_internal(x, y, color);
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
}
 | 
			
		||||
void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, Color color) {
 | 
			
		||||
  const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
 | 
			
		||||
  const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
 | 
			
		||||
  int32_t err = dx + dy;
 | 
			
		||||
 | 
			
		||||
  while (true) {
 | 
			
		||||
    this->draw_pixel_at(x1, y1, color);
 | 
			
		||||
    if (x1 == x2 && y1 == y2)
 | 
			
		||||
      break;
 | 
			
		||||
    int32_t e2 = 2 * err;
 | 
			
		||||
    if (e2 >= dy) {
 | 
			
		||||
      err += dy;
 | 
			
		||||
      x1 += sx;
 | 
			
		||||
    }
 | 
			
		||||
    if (e2 <= dx) {
 | 
			
		||||
      err += dx;
 | 
			
		||||
      y1 += sy;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void HOT DisplayBuffer::horizontal_line(int x, int y, int width, Color color) {
 | 
			
		||||
  // Future: Could be made more efficient by manipulating buffer directly in certain rotations.
 | 
			
		||||
  for (int i = x; i < x + width; i++)
 | 
			
		||||
    this->draw_pixel_at(i, y, color);
 | 
			
		||||
}
 | 
			
		||||
void HOT DisplayBuffer::vertical_line(int x, int y, int height, Color color) {
 | 
			
		||||
  // Future: Could be made more efficient by manipulating buffer directly in certain rotations.
 | 
			
		||||
  for (int i = y; i < y + height; i++)
 | 
			
		||||
    this->draw_pixel_at(x, i, color);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::rectangle(int x1, int y1, int width, int height, Color color) {
 | 
			
		||||
  this->horizontal_line(x1, y1, width, color);
 | 
			
		||||
  this->horizontal_line(x1, y1 + height - 1, width, color);
 | 
			
		||||
  this->vertical_line(x1, y1, height, color);
 | 
			
		||||
  this->vertical_line(x1 + width - 1, y1, height, color);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, Color color) {
 | 
			
		||||
  // Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
 | 
			
		||||
  for (int i = y1; i < y1 + height; i++) {
 | 
			
		||||
    this->horizontal_line(x1, i, width, color);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, Color color) {
 | 
			
		||||
  int dx = -radius;
 | 
			
		||||
  int dy = 0;
 | 
			
		||||
  int err = 2 - 2 * radius;
 | 
			
		||||
  int e2;
 | 
			
		||||
 | 
			
		||||
  do {
 | 
			
		||||
    this->draw_pixel_at(center_x - dx, center_xy + dy, color);
 | 
			
		||||
    this->draw_pixel_at(center_x + dx, center_xy + dy, color);
 | 
			
		||||
    this->draw_pixel_at(center_x + dx, center_xy - dy, color);
 | 
			
		||||
    this->draw_pixel_at(center_x - dx, center_xy - dy, color);
 | 
			
		||||
    e2 = err;
 | 
			
		||||
    if (e2 < dy) {
 | 
			
		||||
      err += ++dy * 2 + 1;
 | 
			
		||||
      if (-dx == dy && e2 <= dx) {
 | 
			
		||||
        e2 = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (e2 > dx) {
 | 
			
		||||
      err += ++dx * 2 + 1;
 | 
			
		||||
    }
 | 
			
		||||
  } while (dx <= 0);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, Color color) {
 | 
			
		||||
  int dx = -int32_t(radius);
 | 
			
		||||
  int dy = 0;
 | 
			
		||||
  int err = 2 - 2 * radius;
 | 
			
		||||
  int e2;
 | 
			
		||||
 | 
			
		||||
  do {
 | 
			
		||||
    this->draw_pixel_at(center_x - dx, center_y + dy, color);
 | 
			
		||||
    this->draw_pixel_at(center_x + dx, center_y + dy, color);
 | 
			
		||||
    this->draw_pixel_at(center_x + dx, center_y - dy, color);
 | 
			
		||||
    this->draw_pixel_at(center_x - dx, center_y - dy, color);
 | 
			
		||||
    int hline_width = 2 * (-dx) + 1;
 | 
			
		||||
    this->horizontal_line(center_x + dx, center_y + dy, hline_width, color);
 | 
			
		||||
    this->horizontal_line(center_x + dx, center_y - dy, hline_width, color);
 | 
			
		||||
    e2 = err;
 | 
			
		||||
    if (e2 < dy) {
 | 
			
		||||
      err += ++dy * 2 + 1;
 | 
			
		||||
      if (-dx == dy && e2 <= dx) {
 | 
			
		||||
        e2 = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (e2 > dx) {
 | 
			
		||||
      err += ++dx * 2 + 1;
 | 
			
		||||
    }
 | 
			
		||||
  } while (dx <= 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align, const char *text) {
 | 
			
		||||
  int x_start, y_start;
 | 
			
		||||
  int width, height;
 | 
			
		||||
  this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
 | 
			
		||||
 | 
			
		||||
  int i = 0;
 | 
			
		||||
  int x_at = x_start;
 | 
			
		||||
  while (text[i] != '\0') {
 | 
			
		||||
    int match_length;
 | 
			
		||||
    int glyph_n = font->match_next_glyph(text + i, &match_length);
 | 
			
		||||
    if (glyph_n < 0) {
 | 
			
		||||
      // Unknown char, skip
 | 
			
		||||
      ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]);
 | 
			
		||||
      if (!font->get_glyphs().empty()) {
 | 
			
		||||
        uint8_t glyph_width = font->get_glyphs()[0].glyph_data_->width;
 | 
			
		||||
        for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++) {
 | 
			
		||||
          for (int glyph_y = 0; glyph_y < height; glyph_y++)
 | 
			
		||||
            this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
 | 
			
		||||
        }
 | 
			
		||||
        x_at += glyph_width;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      i++;
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const Glyph &glyph = font->get_glyphs()[glyph_n];
 | 
			
		||||
    int scan_x1, scan_y1, scan_width, scan_height;
 | 
			
		||||
    glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
 | 
			
		||||
 | 
			
		||||
    for (int glyph_x = scan_x1; glyph_x < scan_x1 + scan_width; glyph_x++) {
 | 
			
		||||
      for (int glyph_y = scan_y1; glyph_y < scan_y1 + scan_height; glyph_y++) {
 | 
			
		||||
        if (glyph.get_pixel(glyph_x, glyph_y)) {
 | 
			
		||||
          this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
 | 
			
		||||
 | 
			
		||||
    i += match_length;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg) {
 | 
			
		||||
  char buffer[256];
 | 
			
		||||
  int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
 | 
			
		||||
  if (ret > 0)
 | 
			
		||||
    this->print(x, y, font, color, align, buffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color color_off) {
 | 
			
		||||
  switch (image->get_type()) {
 | 
			
		||||
    case IMAGE_TYPE_BINARY:
 | 
			
		||||
      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
			
		||||
        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
			
		||||
          this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? color_on : color_off);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case IMAGE_TYPE_GRAYSCALE:
 | 
			
		||||
      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
			
		||||
        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
			
		||||
          this->draw_pixel_at(x + img_x, y + img_y, image->get_grayscale_pixel(img_x, img_y));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case IMAGE_TYPE_RGB24:
 | 
			
		||||
      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
			
		||||
        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
			
		||||
          this->draw_pixel_at(x + img_x, y + img_y, image->get_color_pixel(img_x, img_y));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case IMAGE_TYPE_TRANSPARENT_BINARY:
 | 
			
		||||
      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
			
		||||
        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
			
		||||
          if (image->get_pixel(img_x, img_y))
 | 
			
		||||
            this->draw_pixel_at(x + img_x, y + img_y, color_on);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case IMAGE_TYPE_RGB565:
 | 
			
		||||
      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
			
		||||
        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
			
		||||
          this->draw_pixel_at(x + img_x, y + img_y, image->get_rgb565_pixel(img_x, img_y));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_GRAPH
 | 
			
		||||
void DisplayBuffer::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); }
 | 
			
		||||
void DisplayBuffer::legend(int x, int y, graph::Graph *graph, Color color_on) {
 | 
			
		||||
  graph->draw_legend(this, x, y, color_on);
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_GRAPH
 | 
			
		||||
 | 
			
		||||
#ifdef USE_QR_CODE
 | 
			
		||||
void DisplayBuffer::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) {
 | 
			
		||||
  qr_code->draw(this, x, y, color_on, scale);
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_QR_CODE
 | 
			
		||||
 | 
			
		||||
void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1,
 | 
			
		||||
                                    int *width, int *height) {
 | 
			
		||||
  int x_offset, baseline;
 | 
			
		||||
  font->measure(text, width, &x_offset, &baseline, height);
 | 
			
		||||
 | 
			
		||||
  auto x_align = TextAlign(int(align) & 0x18);
 | 
			
		||||
  auto y_align = TextAlign(int(align) & 0x07);
 | 
			
		||||
 | 
			
		||||
  switch (x_align) {
 | 
			
		||||
    case TextAlign::RIGHT:
 | 
			
		||||
      *x1 = x - *width;
 | 
			
		||||
      break;
 | 
			
		||||
    case TextAlign::CENTER_HORIZONTAL:
 | 
			
		||||
      *x1 = x - (*width) / 2;
 | 
			
		||||
      break;
 | 
			
		||||
    case TextAlign::LEFT:
 | 
			
		||||
    default:
 | 
			
		||||
      // LEFT
 | 
			
		||||
      *x1 = x;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (y_align) {
 | 
			
		||||
    case TextAlign::BOTTOM:
 | 
			
		||||
      *y1 = y - *height;
 | 
			
		||||
      break;
 | 
			
		||||
    case TextAlign::BASELINE:
 | 
			
		||||
      *y1 = y - baseline;
 | 
			
		||||
      break;
 | 
			
		||||
    case TextAlign::CENTER_VERTICAL:
 | 
			
		||||
      *y1 = y - (*height) / 2;
 | 
			
		||||
      break;
 | 
			
		||||
    case TextAlign::TOP:
 | 
			
		||||
    default:
 | 
			
		||||
      *y1 = y;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::print(int x, int y, Font *font, Color color, const char *text) {
 | 
			
		||||
  this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char *text) {
 | 
			
		||||
  this->print(x, y, font, COLOR_ON, align, text);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::print(int x, int y, Font *font, const char *text) {
 | 
			
		||||
  this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::printf(int x, int y, Font *font, Color color, TextAlign align, const char *format, ...) {
 | 
			
		||||
  va_list arg;
 | 
			
		||||
  va_start(arg, format);
 | 
			
		||||
  this->vprintf_(x, y, font, color, align, format, arg);
 | 
			
		||||
  va_end(arg);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::printf(int x, int y, Font *font, Color color, const char *format, ...) {
 | 
			
		||||
  va_list arg;
 | 
			
		||||
  va_start(arg, format);
 | 
			
		||||
  this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
 | 
			
		||||
  va_end(arg);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::printf(int x, int y, Font *font, TextAlign align, const char *format, ...) {
 | 
			
		||||
  va_list arg;
 | 
			
		||||
  va_start(arg, format);
 | 
			
		||||
  this->vprintf_(x, y, font, COLOR_ON, align, format, arg);
 | 
			
		||||
  va_end(arg);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::printf(int x, int y, Font *font, const char *format, ...) {
 | 
			
		||||
  va_list arg;
 | 
			
		||||
  va_start(arg, format);
 | 
			
		||||
  this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg);
 | 
			
		||||
  va_end(arg);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
 | 
			
		||||
void DisplayBuffer::set_pages(std::vector<DisplayPage *> pages) {
 | 
			
		||||
  for (auto *page : pages)
 | 
			
		||||
    page->set_parent(this);
 | 
			
		||||
 | 
			
		||||
  for (uint32_t i = 0; i < pages.size() - 1; i++) {
 | 
			
		||||
    pages[i]->set_next(pages[i + 1]);
 | 
			
		||||
    pages[i + 1]->set_prev(pages[i]);
 | 
			
		||||
  }
 | 
			
		||||
  pages[0]->set_prev(pages[pages.size() - 1]);
 | 
			
		||||
  pages[pages.size() - 1]->set_next(pages[0]);
 | 
			
		||||
  this->show_page(pages[0]);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::show_page(DisplayPage *page) {
 | 
			
		||||
  this->previous_page_ = this->page_;
 | 
			
		||||
  this->page_ = page;
 | 
			
		||||
  if (this->previous_page_ != this->page_) {
 | 
			
		||||
    for (auto *t : on_page_change_triggers_)
 | 
			
		||||
      t->process(this->previous_page_, this->page_);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::show_next_page() { this->page_->show_next(); }
 | 
			
		||||
void DisplayBuffer::show_prev_page() { this->page_->show_prev(); }
 | 
			
		||||
void DisplayBuffer::do_update_() {
 | 
			
		||||
  if (this->auto_clear_enabled_) {
 | 
			
		||||
    this->clear();
 | 
			
		||||
  }
 | 
			
		||||
  if (this->page_ != nullptr) {
 | 
			
		||||
    this->page_->get_writer()(*this);
 | 
			
		||||
  } else if (this->writer_.has_value()) {
 | 
			
		||||
    (*this->writer_)(*this);
 | 
			
		||||
  }
 | 
			
		||||
  // remove all not ended clipping regions
 | 
			
		||||
  while (is_clipping()) {
 | 
			
		||||
    end_clipping();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
 | 
			
		||||
  if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
 | 
			
		||||
    this->trigger(from, to);
 | 
			
		||||
}
 | 
			
		||||
#ifdef USE_TIME
 | 
			
		||||
void DisplayBuffer::strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format,
 | 
			
		||||
                             time::ESPTime time) {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  size_t ret = time.strftime(buffer, sizeof(buffer), format);
 | 
			
		||||
  if (ret > 0)
 | 
			
		||||
    this->print(x, y, font, color, align, buffer);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time) {
 | 
			
		||||
  this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) {
 | 
			
		||||
  this->strftime(x, y, font, COLOR_ON, align, format, time);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, time::ESPTime time) {
 | 
			
		||||
  this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void DisplayBuffer::start_clipping(Rect rect) {
 | 
			
		||||
  if (!this->clipping_rectangle_.empty()) {
 | 
			
		||||
    Rect r = this->clipping_rectangle_.back();
 | 
			
		||||
    rect.shrink(r);
 | 
			
		||||
  }
 | 
			
		||||
  this->clipping_rectangle_.push_back(rect);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::end_clipping() {
 | 
			
		||||
  if (this->clipping_rectangle_.empty()) {
 | 
			
		||||
    ESP_LOGE(TAG, "clear: Clipping is not set.");
 | 
			
		||||
  } else {
 | 
			
		||||
    this->clipping_rectangle_.pop_back();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::extend_clipping(Rect add_rect) {
 | 
			
		||||
  if (this->clipping_rectangle_.empty()) {
 | 
			
		||||
    ESP_LOGE(TAG, "add: Clipping is not set.");
 | 
			
		||||
  } else {
 | 
			
		||||
    this->clipping_rectangle_.back().extend(add_rect);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::shrink_clipping(Rect add_rect) {
 | 
			
		||||
  if (this->clipping_rectangle_.empty()) {
 | 
			
		||||
    ESP_LOGE(TAG, "add: Clipping is not set.");
 | 
			
		||||
  } else {
 | 
			
		||||
    this->clipping_rectangle_.back().shrink(add_rect);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
Rect DisplayBuffer::get_clipping() {
 | 
			
		||||
  if (this->clipping_rectangle_.empty()) {
 | 
			
		||||
    return Rect();
 | 
			
		||||
  } else {
 | 
			
		||||
    return this->clipping_rectangle_.back();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool Glyph::get_pixel(int x, int y) const {
 | 
			
		||||
  const int x_data = x - this->glyph_data_->offset_x;
 | 
			
		||||
  const int y_data = y - this->glyph_data_->offset_y;
 | 
			
		||||
  if (x_data < 0 || x_data >= this->glyph_data_->width || y_data < 0 || y_data >= this->glyph_data_->height)
 | 
			
		||||
    return false;
 | 
			
		||||
  const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u;
 | 
			
		||||
  const uint32_t pos = x_data + y_data * width_8;
 | 
			
		||||
  return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u));
 | 
			
		||||
}
 | 
			
		||||
const char *Glyph::get_char() const { return this->glyph_data_->a_char; }
 | 
			
		||||
bool Glyph::compare_to(const char *str) const {
 | 
			
		||||
  // 1 -> this->char_
 | 
			
		||||
  // 2 -> str
 | 
			
		||||
  for (uint32_t i = 0;; i++) {
 | 
			
		||||
    if (this->glyph_data_->a_char[i] == '\0')
 | 
			
		||||
      return true;
 | 
			
		||||
    if (str[i] == '\0')
 | 
			
		||||
      return false;
 | 
			
		||||
    if (this->glyph_data_->a_char[i] > str[i])
 | 
			
		||||
      return false;
 | 
			
		||||
    if (this->glyph_data_->a_char[i] < str[i])
 | 
			
		||||
      return true;
 | 
			
		||||
  }
 | 
			
		||||
  // this should not happen
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
int Glyph::match_length(const char *str) const {
 | 
			
		||||
  for (uint32_t i = 0;; i++) {
 | 
			
		||||
    if (this->glyph_data_->a_char[i] == '\0')
 | 
			
		||||
      return i;
 | 
			
		||||
    if (str[i] != this->glyph_data_->a_char[i])
 | 
			
		||||
      return 0;
 | 
			
		||||
  }
 | 
			
		||||
  // this should not happen
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
 | 
			
		||||
  *x1 = this->glyph_data_->offset_x;
 | 
			
		||||
  *y1 = this->glyph_data_->offset_y;
 | 
			
		||||
  *width = this->glyph_data_->width;
 | 
			
		||||
  *height = this->glyph_data_->height;
 | 
			
		||||
}
 | 
			
		||||
int Font::match_next_glyph(const char *str, int *match_length) {
 | 
			
		||||
  int lo = 0;
 | 
			
		||||
  int hi = this->glyphs_.size() - 1;
 | 
			
		||||
  while (lo != hi) {
 | 
			
		||||
    int mid = (lo + hi + 1) / 2;
 | 
			
		||||
    if (this->glyphs_[mid].compare_to(str)) {
 | 
			
		||||
      lo = mid;
 | 
			
		||||
    } else {
 | 
			
		||||
      hi = mid - 1;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  *match_length = this->glyphs_[lo].match_length(str);
 | 
			
		||||
  if (*match_length <= 0)
 | 
			
		||||
    return -1;
 | 
			
		||||
  return lo;
 | 
			
		||||
}
 | 
			
		||||
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
 | 
			
		||||
  *baseline = this->baseline_;
 | 
			
		||||
  *height = this->height_;
 | 
			
		||||
  int i = 0;
 | 
			
		||||
  int min_x = 0;
 | 
			
		||||
  bool has_char = false;
 | 
			
		||||
  int x = 0;
 | 
			
		||||
  while (str[i] != '\0') {
 | 
			
		||||
    int match_length;
 | 
			
		||||
    int glyph_n = this->match_next_glyph(str + i, &match_length);
 | 
			
		||||
    if (glyph_n < 0) {
 | 
			
		||||
      // Unknown char, skip
 | 
			
		||||
      if (!this->get_glyphs().empty())
 | 
			
		||||
        x += this->get_glyphs()[0].glyph_data_->width;
 | 
			
		||||
      i++;
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const Glyph &glyph = this->glyphs_[glyph_n];
 | 
			
		||||
    if (!has_char) {
 | 
			
		||||
      min_x = glyph.glyph_data_->offset_x;
 | 
			
		||||
    } else {
 | 
			
		||||
      min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
 | 
			
		||||
    }
 | 
			
		||||
    x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
 | 
			
		||||
 | 
			
		||||
    i += match_length;
 | 
			
		||||
    has_char = true;
 | 
			
		||||
  }
 | 
			
		||||
  *x_offset = min_x;
 | 
			
		||||
  *width = x - min_x;
 | 
			
		||||
}
 | 
			
		||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) {
 | 
			
		||||
  glyphs_.reserve(data_nr);
 | 
			
		||||
  for (int i = 0; i < data_nr; ++i)
 | 
			
		||||
    glyphs_.emplace_back(&data[i]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Image::get_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return false;
 | 
			
		||||
  const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
 | 
			
		||||
  const uint32_t pos = x + y * width_8;
 | 
			
		||||
  return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
 | 
			
		||||
}
 | 
			
		||||
Color Image::get_color_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return Color::BLACK;
 | 
			
		||||
  const uint32_t pos = (x + y * this->width_) * 3;
 | 
			
		||||
  const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) |
 | 
			
		||||
                           (progmem_read_byte(this->data_start_ + pos + 1) << 8) |
 | 
			
		||||
                           (progmem_read_byte(this->data_start_ + pos + 0) << 16);
 | 
			
		||||
  return Color(color32);
 | 
			
		||||
}
 | 
			
		||||
Color Image::get_rgb565_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return Color::BLACK;
 | 
			
		||||
  const uint32_t pos = (x + y * this->width_) * 2;
 | 
			
		||||
  uint16_t rgb565 =
 | 
			
		||||
      progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
 | 
			
		||||
  auto r = (rgb565 & 0xF800) >> 11;
 | 
			
		||||
  auto g = (rgb565 & 0x07E0) >> 5;
 | 
			
		||||
  auto b = rgb565 & 0x001F;
 | 
			
		||||
  return Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2));
 | 
			
		||||
}
 | 
			
		||||
Color Image::get_grayscale_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return Color::BLACK;
 | 
			
		||||
  const uint32_t pos = (x + y * this->width_);
 | 
			
		||||
  const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
 | 
			
		||||
  return Color(gray | gray << 8 | gray << 16 | gray << 24);
 | 
			
		||||
}
 | 
			
		||||
int Image::get_width() const { return this->width_; }
 | 
			
		||||
int Image::get_height() const { return this->height_; }
 | 
			
		||||
ImageType Image::get_type() const { return this->type_; }
 | 
			
		||||
Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
 | 
			
		||||
    : width_(width), height_(height), type_(type), data_start_(data_start) {}
 | 
			
		||||
int Image::get_current_frame() const { return 0; }
 | 
			
		||||
 | 
			
		||||
bool Animation::get_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return false;
 | 
			
		||||
  const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
 | 
			
		||||
  const uint32_t frame_index = this->height_ * width_8 * this->current_frame_;
 | 
			
		||||
  if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_))
 | 
			
		||||
    return false;
 | 
			
		||||
  const uint32_t pos = x + y * width_8 + frame_index;
 | 
			
		||||
  return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
 | 
			
		||||
}
 | 
			
		||||
Color Animation::get_color_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return Color::BLACK;
 | 
			
		||||
  const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
 | 
			
		||||
  if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_))
 | 
			
		||||
    return Color::BLACK;
 | 
			
		||||
  const uint32_t pos = (x + y * this->width_ + frame_index) * 3;
 | 
			
		||||
  const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) |
 | 
			
		||||
                           (progmem_read_byte(this->data_start_ + pos + 1) << 8) |
 | 
			
		||||
                           (progmem_read_byte(this->data_start_ + pos + 0) << 16);
 | 
			
		||||
  return Color(color32);
 | 
			
		||||
}
 | 
			
		||||
Color Animation::get_rgb565_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return Color::BLACK;
 | 
			
		||||
  const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
 | 
			
		||||
  if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_))
 | 
			
		||||
    return Color::BLACK;
 | 
			
		||||
  const uint32_t pos = (x + y * this->width_ + frame_index) * 2;
 | 
			
		||||
  uint16_t rgb565 =
 | 
			
		||||
      progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
 | 
			
		||||
  auto r = (rgb565 & 0xF800) >> 11;
 | 
			
		||||
  auto g = (rgb565 & 0x07E0) >> 5;
 | 
			
		||||
  auto b = rgb565 & 0x001F;
 | 
			
		||||
  return Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2));
 | 
			
		||||
}
 | 
			
		||||
Color Animation::get_grayscale_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return Color::BLACK;
 | 
			
		||||
  const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
 | 
			
		||||
  if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_))
 | 
			
		||||
    return Color::BLACK;
 | 
			
		||||
  const uint32_t pos = (x + y * this->width_ + frame_index);
 | 
			
		||||
  const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
 | 
			
		||||
  return Color(gray | gray << 8 | gray << 16 | gray << 24);
 | 
			
		||||
}
 | 
			
		||||
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type)
 | 
			
		||||
    : Image(data_start, width, height, type), current_frame_(0), animation_frame_count_(animation_frame_count) {}
 | 
			
		||||
int Animation::get_animation_frame_count() const { return this->animation_frame_count_; }
 | 
			
		||||
int Animation::get_current_frame() const { return this->current_frame_; }
 | 
			
		||||
void Animation::next_frame() {
 | 
			
		||||
  this->current_frame_++;
 | 
			
		||||
  if (this->current_frame_ >= animation_frame_count_) {
 | 
			
		||||
    this->current_frame_ = 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void Animation::prev_frame() {
 | 
			
		||||
  this->current_frame_--;
 | 
			
		||||
  if (this->current_frame_ < 0) {
 | 
			
		||||
    this->current_frame_ = this->animation_frame_count_ - 1;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Animation::set_frame(int frame) {
 | 
			
		||||
  unsigned abs_frame = abs(frame);
 | 
			
		||||
 | 
			
		||||
  if (abs_frame < this->animation_frame_count_) {
 | 
			
		||||
    if (frame >= 0) {
 | 
			
		||||
      this->current_frame_ = frame;
 | 
			
		||||
    } else {
 | 
			
		||||
      this->current_frame_ = this->animation_frame_count_ - abs_frame;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
 | 
			
		||||
void DisplayPage::show() { this->parent_->show_page(this); }
 | 
			
		||||
void DisplayPage::show_next() { this->next_->show(); }
 | 
			
		||||
void DisplayPage::show_prev() { this->prev_->show(); }
 | 
			
		||||
void DisplayPage::set_parent(DisplayBuffer *parent) { this->parent_ = parent; }
 | 
			
		||||
void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
 | 
			
		||||
void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
 | 
			
		||||
const display_writer_t &DisplayPage::get_writer() const { return this->writer_; }
 | 
			
		||||
 | 
			
		||||
}  // namespace display
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -1,36 +1,636 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <cstdarg>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "display.h"
 | 
			
		||||
#include "display_color_utils.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "display_color_utils.h"
 | 
			
		||||
#include <cstdarg>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TIME
 | 
			
		||||
#include "esphome/components/time/real_time_clock.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_GRAPH
 | 
			
		||||
#include "esphome/components/graph/graph.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_QR_CODE
 | 
			
		||||
#include "esphome/components/qr_code/qr_code.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display {
 | 
			
		||||
 | 
			
		||||
class DisplayBuffer : public Display {
 | 
			
		||||
/** TextAlign is used to tell the display class how to position a piece of text. By default
 | 
			
		||||
 * the coordinates you enter for the print*() functions take the upper left corner of the text
 | 
			
		||||
 * as the "anchor" point. You can customize this behavior to, for example, make the coordinates
 | 
			
		||||
 * refer to the *center* of the text.
 | 
			
		||||
 *
 | 
			
		||||
 * All text alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
 | 
			
		||||
 * these options are allowed:
 | 
			
		||||
 *
 | 
			
		||||
 * - LEFT (x-coordinate of anchor point is on left)
 | 
			
		||||
 * - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the text)
 | 
			
		||||
 * - RIGHT (x-coordinate of anchor point is on right)
 | 
			
		||||
 *
 | 
			
		||||
 * For the Y-Axis alignment these options are allowed:
 | 
			
		||||
 *
 | 
			
		||||
 * - TOP (y-coordinate of anchor is on the top of the text)
 | 
			
		||||
 * - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the text)
 | 
			
		||||
 * - BASELINE (y-coordinate of anchor is on the baseline of the text)
 | 
			
		||||
 * - BOTTOM (y-coordinate of anchor is on the bottom of the text)
 | 
			
		||||
 *
 | 
			
		||||
 * These options are then combined to create combined TextAlignment options like:
 | 
			
		||||
 * - TOP_LEFT (default)
 | 
			
		||||
 * - CENTER (anchor point is in the middle of the text bounds)
 | 
			
		||||
 * - ...
 | 
			
		||||
 */
 | 
			
		||||
enum class TextAlign {
 | 
			
		||||
  TOP = 0x00,
 | 
			
		||||
  CENTER_VERTICAL = 0x01,
 | 
			
		||||
  BASELINE = 0x02,
 | 
			
		||||
  BOTTOM = 0x04,
 | 
			
		||||
 | 
			
		||||
  LEFT = 0x00,
 | 
			
		||||
  CENTER_HORIZONTAL = 0x08,
 | 
			
		||||
  RIGHT = 0x10,
 | 
			
		||||
 | 
			
		||||
  TOP_LEFT = TOP | LEFT,
 | 
			
		||||
  TOP_CENTER = TOP | CENTER_HORIZONTAL,
 | 
			
		||||
  TOP_RIGHT = TOP | RIGHT,
 | 
			
		||||
 | 
			
		||||
  CENTER_LEFT = CENTER_VERTICAL | LEFT,
 | 
			
		||||
  CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
 | 
			
		||||
  CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
 | 
			
		||||
 | 
			
		||||
  BASELINE_LEFT = BASELINE | LEFT,
 | 
			
		||||
  BASELINE_CENTER = BASELINE | CENTER_HORIZONTAL,
 | 
			
		||||
  BASELINE_RIGHT = BASELINE | RIGHT,
 | 
			
		||||
 | 
			
		||||
  BOTTOM_LEFT = BOTTOM | LEFT,
 | 
			
		||||
  BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
 | 
			
		||||
  BOTTOM_RIGHT = BOTTOM | RIGHT,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Turn the pixel OFF.
 | 
			
		||||
extern const Color COLOR_OFF;
 | 
			
		||||
/// Turn the pixel ON.
 | 
			
		||||
extern const Color COLOR_ON;
 | 
			
		||||
 | 
			
		||||
enum ImageType {
 | 
			
		||||
  IMAGE_TYPE_BINARY = 0,
 | 
			
		||||
  IMAGE_TYPE_GRAYSCALE = 1,
 | 
			
		||||
  IMAGE_TYPE_RGB24 = 2,
 | 
			
		||||
  IMAGE_TYPE_TRANSPARENT_BINARY = 3,
 | 
			
		||||
  IMAGE_TYPE_RGB565 = 4,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum DisplayType {
 | 
			
		||||
  DISPLAY_TYPE_BINARY = 1,
 | 
			
		||||
  DISPLAY_TYPE_GRAYSCALE = 2,
 | 
			
		||||
  DISPLAY_TYPE_COLOR = 3,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum DisplayRotation {
 | 
			
		||||
  DISPLAY_ROTATION_0_DEGREES = 0,
 | 
			
		||||
  DISPLAY_ROTATION_90_DEGREES = 90,
 | 
			
		||||
  DISPLAY_ROTATION_180_DEGREES = 180,
 | 
			
		||||
  DISPLAY_ROTATION_270_DEGREES = 270,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const int16_t VALUE_NO_SET = 32766;
 | 
			
		||||
 | 
			
		||||
class Rect {
 | 
			
		||||
 public:
 | 
			
		||||
  int16_t x;  ///< X coordinate of corner
 | 
			
		||||
  int16_t y;  ///< Y coordinate of corner
 | 
			
		||||
  int16_t w;  ///< Width of region
 | 
			
		||||
  int16_t h;  ///< Height of region
 | 
			
		||||
 | 
			
		||||
  Rect() : x(VALUE_NO_SET), y(VALUE_NO_SET), w(VALUE_NO_SET), h(VALUE_NO_SET) {}  // NOLINT
 | 
			
		||||
  inline Rect(int16_t x, int16_t y, int16_t w, int16_t h) ALWAYS_INLINE : x(x), y(y), w(w), h(h) {}
 | 
			
		||||
  inline int16_t x2() { return this->x + this->w; };  ///< X coordinate of corner
 | 
			
		||||
  inline int16_t y2() { return this->y + this->h; };  ///< Y coordinate of corner
 | 
			
		||||
 | 
			
		||||
  inline bool is_set() ALWAYS_INLINE { return (this->h != VALUE_NO_SET) && (this->w != VALUE_NO_SET); }
 | 
			
		||||
 | 
			
		||||
  void expand(int16_t horizontal, int16_t vertical);
 | 
			
		||||
 | 
			
		||||
  void extend(Rect rect);
 | 
			
		||||
  void shrink(Rect rect);
 | 
			
		||||
 | 
			
		||||
  bool inside(Rect rect, bool absolute = true);
 | 
			
		||||
  bool inside(int16_t test_x, int16_t test_y, bool absolute = true);
 | 
			
		||||
  bool equal(Rect rect);
 | 
			
		||||
  void info(const std::string &prefix = "rect info:");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Font;
 | 
			
		||||
class Image;
 | 
			
		||||
class DisplayBuffer;
 | 
			
		||||
class DisplayPage;
 | 
			
		||||
class DisplayOnPageChangeTrigger;
 | 
			
		||||
 | 
			
		||||
using display_writer_t = std::function<void(DisplayBuffer &)>;
 | 
			
		||||
 | 
			
		||||
#define LOG_DISPLAY(prefix, type, obj) \
 | 
			
		||||
  if ((obj) != nullptr) { \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, prefix type); \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s  Rotations: %d °", prefix, (obj)->rotation_); \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s  Dimensions: %dpx x %dpx", prefix, (obj)->get_width(), (obj)->get_height()); \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
class DisplayBuffer {
 | 
			
		||||
 public:
 | 
			
		||||
  /// Fill the entire screen with the given color.
 | 
			
		||||
  virtual void fill(Color color);
 | 
			
		||||
  /// Clear the entire screen by filling it with OFF pixels.
 | 
			
		||||
  void clear();
 | 
			
		||||
 | 
			
		||||
  /// Get the width of the image in pixels with rotation applied.
 | 
			
		||||
  int get_width() override;
 | 
			
		||||
  int get_width();
 | 
			
		||||
  /// Get the height of the image in pixels with rotation applied.
 | 
			
		||||
  int get_height() override;
 | 
			
		||||
  int get_height();
 | 
			
		||||
 | 
			
		||||
  /// Set a single pixel at the specified coordinates to the given color.
 | 
			
		||||
  void draw_pixel_at(int x, int y, Color color) override;
 | 
			
		||||
  void draw_pixel_at(int x, int y, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
 | 
			
		||||
  void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
 | 
			
		||||
  void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /// Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
 | 
			
		||||
  void vertical_line(int x, int y, int height, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at
 | 
			
		||||
  /// [x1+width,y1+height].
 | 
			
		||||
  void rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height].
 | 
			
		||||
  void filled_rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color.
 | 
			
		||||
  void circle(int center_x, int center_xy, int radius, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
 | 
			
		||||
  void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /** Print `text` with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param y The y coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param color The color to draw the text with.
 | 
			
		||||
   * @param align The alignment of the text.
 | 
			
		||||
   * @param text The text to draw.
 | 
			
		||||
   */
 | 
			
		||||
  void print(int x, int y, Font *font, Color color, TextAlign align, const char *text);
 | 
			
		||||
 | 
			
		||||
  /** Print `text` with the top left at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param color The color to draw the text with.
 | 
			
		||||
   * @param text The text to draw.
 | 
			
		||||
   */
 | 
			
		||||
  void print(int x, int y, Font *font, Color color, const char *text);
 | 
			
		||||
 | 
			
		||||
  /** Print `text` with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param y The y coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param align The alignment of the text.
 | 
			
		||||
   * @param text The text to draw.
 | 
			
		||||
   */
 | 
			
		||||
  void print(int x, int y, Font *font, TextAlign align, const char *text);
 | 
			
		||||
 | 
			
		||||
  /** Print `text` with the top left at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param text The text to draw.
 | 
			
		||||
   */
 | 
			
		||||
  void print(int x, int y, Font *font, const char *text);
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param y The y coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param color The color to draw the text with.
 | 
			
		||||
   * @param align The alignment of the text.
 | 
			
		||||
   * @param format The format to use.
 | 
			
		||||
   * @param ... The arguments to use for the text formatting.
 | 
			
		||||
   */
 | 
			
		||||
  void printf(int x, int y, Font *font, Color color, TextAlign align, const char *format, ...)
 | 
			
		||||
      __attribute__((format(printf, 7, 8)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param color The color to draw the text with.
 | 
			
		||||
   * @param format The format to use.
 | 
			
		||||
   * @param ... The arguments to use for the text formatting.
 | 
			
		||||
   */
 | 
			
		||||
  void printf(int x, int y, Font *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param y The y coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param align The alignment of the text.
 | 
			
		||||
   * @param format The format to use.
 | 
			
		||||
   * @param ... The arguments to use for the text formatting.
 | 
			
		||||
   */
 | 
			
		||||
  void printf(int x, int y, Font *font, TextAlign align, const char *format, ...) __attribute__((format(printf, 6, 7)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param format The format to use.
 | 
			
		||||
   * @param ... The arguments to use for the text formatting.
 | 
			
		||||
   */
 | 
			
		||||
  void printf(int x, int y, Font *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TIME
 | 
			
		||||
  /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param y The y coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param color The color to draw the text with.
 | 
			
		||||
   * @param align The alignment of the text.
 | 
			
		||||
   * @param format The strftime format to use.
 | 
			
		||||
   * @param time The time to format.
 | 
			
		||||
   */
 | 
			
		||||
  void strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, time::ESPTime time)
 | 
			
		||||
      __attribute__((format(strftime, 7, 0)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param color The color to draw the text with.
 | 
			
		||||
   * @param format The strftime format to use.
 | 
			
		||||
   * @param time The time to format.
 | 
			
		||||
   */
 | 
			
		||||
  void strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time)
 | 
			
		||||
      __attribute__((format(strftime, 6, 0)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param y The y coordinate of the text alignment anchor point.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param align The alignment of the text.
 | 
			
		||||
   * @param format The strftime format to use.
 | 
			
		||||
   * @param time The time to format.
 | 
			
		||||
   */
 | 
			
		||||
  void strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time)
 | 
			
		||||
      __attribute__((format(strftime, 6, 0)));
 | 
			
		||||
 | 
			
		||||
  /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param font The font to draw the text with.
 | 
			
		||||
   * @param format The strftime format to use.
 | 
			
		||||
   * @param time The time to format.
 | 
			
		||||
   */
 | 
			
		||||
  void strftime(int x, int y, Font *font, const char *format, time::ESPTime time)
 | 
			
		||||
      __attribute__((format(strftime, 5, 0)));
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  /** Draw the `image` with the top-left corner at [x,y] to the screen.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param image The image to draw
 | 
			
		||||
   * @param color_on The color to replace in binary images for the on bits.
 | 
			
		||||
   * @param color_off The color to replace in binary images for the off bits.
 | 
			
		||||
   */
 | 
			
		||||
  void image(int x, int y, Image *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
 | 
			
		||||
 | 
			
		||||
#ifdef USE_GRAPH
 | 
			
		||||
  /** Draw the `graph` with the top-left corner at [x,y] to the screen.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param graph The graph id to draw
 | 
			
		||||
   * @param color_on The color to replace in binary images for the on bits.
 | 
			
		||||
   */
 | 
			
		||||
  void graph(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
 | 
			
		||||
 | 
			
		||||
  /** Draw the `legend` for graph with the top-left corner at [x,y] to the screen.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param graph The graph id for which the legend applies to
 | 
			
		||||
   * @param graph The graph id for which the legend applies to
 | 
			
		||||
   * @param graph The graph id for which the legend applies to
 | 
			
		||||
   * @param name_font The font used for the trace name
 | 
			
		||||
   * @param value_font The font used for the trace value and units
 | 
			
		||||
   * @param color_on The color of the border
 | 
			
		||||
   */
 | 
			
		||||
  void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
 | 
			
		||||
#endif  // USE_GRAPH
 | 
			
		||||
 | 
			
		||||
#ifdef USE_QR_CODE
 | 
			
		||||
  /** Draw the `qr_code` with the top-left corner at [x,y] to the screen.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param qr_code The qr_code to draw
 | 
			
		||||
   * @param color_on The color to replace in binary images for the on bits.
 | 
			
		||||
   */
 | 
			
		||||
  void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  /** Get the text bounds of the given string.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate to place the string at, can be 0 if only interested in dimensions.
 | 
			
		||||
   * @param y The y coordinate to place the string at, can be 0 if only interested in dimensions.
 | 
			
		||||
   * @param text The text to measure.
 | 
			
		||||
   * @param font The font to measure the text bounds with.
 | 
			
		||||
   * @param align The alignment of the text. Set to TextAlign::TOP_LEFT if only interested in dimensions.
 | 
			
		||||
   * @param x1 A pointer to store the returned x coordinate of the upper left corner in.
 | 
			
		||||
   * @param y1 A pointer to store the returned y coordinate of the upper left corner in.
 | 
			
		||||
   * @param width A pointer to store the returned text width in.
 | 
			
		||||
   * @param height A pointer to store the returned text height in.
 | 
			
		||||
   */
 | 
			
		||||
  void get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1, int *width,
 | 
			
		||||
                       int *height);
 | 
			
		||||
 | 
			
		||||
  /// Internal method to set the display writer lambda.
 | 
			
		||||
  void set_writer(display_writer_t &&writer);
 | 
			
		||||
 | 
			
		||||
  void show_page(DisplayPage *page);
 | 
			
		||||
  void show_next_page();
 | 
			
		||||
  void show_prev_page();
 | 
			
		||||
 | 
			
		||||
  void set_pages(std::vector<DisplayPage *> pages);
 | 
			
		||||
 | 
			
		||||
  const DisplayPage *get_active_page() const { return this->page_; }
 | 
			
		||||
 | 
			
		||||
  void add_on_page_change_trigger(DisplayOnPageChangeTrigger *t) { this->on_page_change_triggers_.push_back(t); }
 | 
			
		||||
 | 
			
		||||
  /// Internal method to set the display rotation with.
 | 
			
		||||
  void set_rotation(DisplayRotation rotation);
 | 
			
		||||
 | 
			
		||||
  // Internal method to set display auto clearing.
 | 
			
		||||
  void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
 | 
			
		||||
 | 
			
		||||
  virtual int get_height_internal() = 0;
 | 
			
		||||
  virtual int get_width_internal() = 0;
 | 
			
		||||
  DisplayRotation get_rotation() const { return this->rotation_; }
 | 
			
		||||
 | 
			
		||||
  /** Get the type of display that the buffer corresponds to. In case of dynamically configurable displays,
 | 
			
		||||
   * returns the type the display is currently configured to.
 | 
			
		||||
   */
 | 
			
		||||
  virtual DisplayType get_display_type() = 0;
 | 
			
		||||
 | 
			
		||||
  /** Set the clipping rectangle for further drawing
 | 
			
		||||
   *
 | 
			
		||||
   * @param[in]  rect:       Pointer to Rect for clipping (or NULL for entire screen)
 | 
			
		||||
   *
 | 
			
		||||
   * return true if success, false if error
 | 
			
		||||
   */
 | 
			
		||||
  void start_clipping(Rect rect);
 | 
			
		||||
  void start_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
 | 
			
		||||
    start_clipping(Rect(left, top, right - left, bottom - top));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /** Add a rectangular region to the invalidation region
 | 
			
		||||
   * - This is usually called when an element has been modified
 | 
			
		||||
   *
 | 
			
		||||
   * @param[in]  rect: Rectangle to add to the invalidation region
 | 
			
		||||
   */
 | 
			
		||||
  void extend_clipping(Rect rect);
 | 
			
		||||
  void extend_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
 | 
			
		||||
    this->extend_clipping(Rect(left, top, right - left, bottom - top));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /** substract a rectangular region to the invalidation region
 | 
			
		||||
   *  - This is usually called when an element has been modified
 | 
			
		||||
   *
 | 
			
		||||
   * @param[in]  rect: Rectangle to add to the invalidation region
 | 
			
		||||
   */
 | 
			
		||||
  void shrink_clipping(Rect rect);
 | 
			
		||||
  void shrink_clipping(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
 | 
			
		||||
    this->shrink_clipping(Rect(left, top, right - left, bottom - top));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /** Reset the invalidation region
 | 
			
		||||
   */
 | 
			
		||||
  void end_clipping();
 | 
			
		||||
 | 
			
		||||
  /** Get the current the clipping rectangle
 | 
			
		||||
   *
 | 
			
		||||
   * return rect for active clipping region
 | 
			
		||||
   */
 | 
			
		||||
  Rect get_clipping();
 | 
			
		||||
 | 
			
		||||
  bool is_clipping() const { return !this->clipping_rectangle_.empty(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
 | 
			
		||||
 | 
			
		||||
  virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0;
 | 
			
		||||
 | 
			
		||||
  void init_internal_(uint32_t buffer_length);
 | 
			
		||||
 | 
			
		||||
  void do_update_();
 | 
			
		||||
 | 
			
		||||
  uint8_t *buffer_{nullptr};
 | 
			
		||||
  DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
 | 
			
		||||
  optional<display_writer_t> writer_{};
 | 
			
		||||
  DisplayPage *page_{nullptr};
 | 
			
		||||
  DisplayPage *previous_page_{nullptr};
 | 
			
		||||
  std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
 | 
			
		||||
  bool auto_clear_enabled_{true};
 | 
			
		||||
  std::vector<Rect> clipping_rectangle_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayPage {
 | 
			
		||||
 public:
 | 
			
		||||
  DisplayPage(display_writer_t writer);
 | 
			
		||||
  void show();
 | 
			
		||||
  void show_next();
 | 
			
		||||
  void show_prev();
 | 
			
		||||
  void set_parent(DisplayBuffer *parent);
 | 
			
		||||
  void set_prev(DisplayPage *prev);
 | 
			
		||||
  void set_next(DisplayPage *next);
 | 
			
		||||
  const display_writer_t &get_writer() const;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayBuffer *parent_;
 | 
			
		||||
  display_writer_t writer_;
 | 
			
		||||
  DisplayPage *prev_{nullptr};
 | 
			
		||||
  DisplayPage *next_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct GlyphData {
 | 
			
		||||
  const char *a_char;
 | 
			
		||||
  const uint8_t *data;
 | 
			
		||||
  int offset_x;
 | 
			
		||||
  int offset_y;
 | 
			
		||||
  int width;
 | 
			
		||||
  int height;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Glyph {
 | 
			
		||||
 public:
 | 
			
		||||
  Glyph(const GlyphData *data) : glyph_data_(data) {}
 | 
			
		||||
 | 
			
		||||
  bool get_pixel(int x, int y) const;
 | 
			
		||||
 | 
			
		||||
  const char *get_char() const;
 | 
			
		||||
 | 
			
		||||
  bool compare_to(const char *str) const;
 | 
			
		||||
 | 
			
		||||
  int match_length(const char *str) const;
 | 
			
		||||
 | 
			
		||||
  void scan_area(int *x1, int *y1, int *width, int *height) const;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  friend Font;
 | 
			
		||||
  friend DisplayBuffer;
 | 
			
		||||
 | 
			
		||||
  const GlyphData *glyph_data_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Font {
 | 
			
		||||
 public:
 | 
			
		||||
  /** Construct the font with the given glyphs.
 | 
			
		||||
   *
 | 
			
		||||
   * @param glyphs A vector of glyphs, must be sorted lexicographically.
 | 
			
		||||
   * @param baseline The y-offset from the top of the text to the baseline.
 | 
			
		||||
   * @param bottom The y-offset from the top of the text to the bottom (i.e. height).
 | 
			
		||||
   */
 | 
			
		||||
  Font(const GlyphData *data, int data_nr, int baseline, int height);
 | 
			
		||||
 | 
			
		||||
  int match_next_glyph(const char *str, int *match_length);
 | 
			
		||||
 | 
			
		||||
  void measure(const char *str, int *width, int *x_offset, int *baseline, int *height);
 | 
			
		||||
  inline int get_baseline() { return this->baseline_; }
 | 
			
		||||
  inline int get_height() { return this->height_; }
 | 
			
		||||
 | 
			
		||||
  const std::vector<Glyph, ExternalRAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::vector<Glyph, ExternalRAMAllocator<Glyph>> glyphs_;
 | 
			
		||||
  int baseline_;
 | 
			
		||||
  int height_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Image {
 | 
			
		||||
 public:
 | 
			
		||||
  Image(const uint8_t *data_start, int width, int height, ImageType type);
 | 
			
		||||
  virtual bool get_pixel(int x, int y) const;
 | 
			
		||||
  virtual Color get_color_pixel(int x, int y) const;
 | 
			
		||||
  virtual Color get_rgb565_pixel(int x, int y) const;
 | 
			
		||||
  virtual Color get_grayscale_pixel(int x, int y) const;
 | 
			
		||||
  int get_width() const;
 | 
			
		||||
  int get_height() const;
 | 
			
		||||
  ImageType get_type() const;
 | 
			
		||||
 | 
			
		||||
  virtual int get_current_frame() const;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  int width_;
 | 
			
		||||
  int height_;
 | 
			
		||||
  ImageType type_;
 | 
			
		||||
  const uint8_t *data_start_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Animation : public Image {
 | 
			
		||||
 public:
 | 
			
		||||
  Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type);
 | 
			
		||||
  bool get_pixel(int x, int y) const override;
 | 
			
		||||
  Color get_color_pixel(int x, int y) const override;
 | 
			
		||||
  Color get_rgb565_pixel(int x, int y) const override;
 | 
			
		||||
  Color get_grayscale_pixel(int x, int y) const override;
 | 
			
		||||
 | 
			
		||||
  int get_animation_frame_count() const;
 | 
			
		||||
  int get_current_frame() const override;
 | 
			
		||||
  void next_frame();
 | 
			
		||||
  void prev_frame();
 | 
			
		||||
 | 
			
		||||
  /** Selects a specific frame within the animation.
 | 
			
		||||
   *
 | 
			
		||||
   * @param frame If possitive, advance to the frame. If negative, recede to that frame from the end frame.
 | 
			
		||||
   */
 | 
			
		||||
  void set_frame(int frame);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  int current_frame_;
 | 
			
		||||
  int animation_frame_count_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  TEMPLATABLE_VALUE(DisplayPage *, page)
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    auto *page = this->page_.value(x...);
 | 
			
		||||
    if (page != nullptr) {
 | 
			
		||||
      page->show();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class DisplayPageShowNextAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  DisplayPageShowNextAction(DisplayBuffer *buffer) : buffer_(buffer) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->buffer_->show_next_page(); }
 | 
			
		||||
 | 
			
		||||
  DisplayBuffer *buffer_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class DisplayPageShowPrevAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  DisplayPageShowPrevAction(DisplayBuffer *buffer) : buffer_(buffer) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->buffer_->show_prev_page(); }
 | 
			
		||||
 | 
			
		||||
  DisplayBuffer *buffer_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class DisplayIsDisplayingPageCondition : public Condition<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  DisplayIsDisplayingPageCondition(DisplayBuffer *parent) : parent_(parent) {}
 | 
			
		||||
 | 
			
		||||
  void set_page(DisplayPage *page) { this->page_ = page; }
 | 
			
		||||
  bool check(Ts... x) override { return this->parent_->get_active_page() == this->page_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayBuffer *parent_;
 | 
			
		||||
  DisplayPage *page_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayOnPageChangeTrigger : public Trigger<DisplayPage *, DisplayPage *> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DisplayOnPageChangeTrigger(DisplayBuffer *parent) { parent->add_on_page_change_trigger(this); }
 | 
			
		||||
  void process(DisplayPage *from, DisplayPage *to);
 | 
			
		||||
  void set_from(DisplayPage *p) { this->from_ = p; }
 | 
			
		||||
  void set_to(DisplayPage *p) { this->to_ = p; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayPage *from_{nullptr};
 | 
			
		||||
  DisplayPage *to_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace display
 | 
			
		||||
 
 | 
			
		||||
@@ -1,98 +0,0 @@
 | 
			
		||||
#include "rect.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "display";
 | 
			
		||||
 | 
			
		||||
void Rect::expand(int16_t horizontal, int16_t vertical) {
 | 
			
		||||
  if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) {
 | 
			
		||||
    this->x = this->x - horizontal;
 | 
			
		||||
    this->y = this->y - vertical;
 | 
			
		||||
    this->w = this->w + (2 * horizontal);
 | 
			
		||||
    this->h = this->h + (2 * vertical);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Rect::extend(Rect rect) {
 | 
			
		||||
  if (!this->is_set()) {
 | 
			
		||||
    this->x = rect.x;
 | 
			
		||||
    this->y = rect.y;
 | 
			
		||||
    this->w = rect.w;
 | 
			
		||||
    this->h = rect.h;
 | 
			
		||||
  } else {
 | 
			
		||||
    if (this->x > rect.x) {
 | 
			
		||||
      this->w = this->w + (this->x - rect.x);
 | 
			
		||||
      this->x = rect.x;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->y > rect.y) {
 | 
			
		||||
      this->h = this->h + (this->y - rect.y);
 | 
			
		||||
      this->y = rect.y;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->x2() < rect.x2()) {
 | 
			
		||||
      this->w = rect.x2() - this->x;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->y2() < rect.y2()) {
 | 
			
		||||
      this->h = rect.y2() - this->y;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void Rect::shrink(Rect rect) {
 | 
			
		||||
  if (!this->inside(rect)) {
 | 
			
		||||
    (*this) = Rect();
 | 
			
		||||
  } else {
 | 
			
		||||
    if (this->x2() > rect.x2()) {
 | 
			
		||||
      this->w = rect.x2() - this->x;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->x < rect.x) {
 | 
			
		||||
      this->w = this->w + (this->x - rect.x);
 | 
			
		||||
      this->x = rect.x;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->y2() > rect.y2()) {
 | 
			
		||||
      this->h = rect.y2() - this->y;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->y < rect.y) {
 | 
			
		||||
      this->h = this->h + (this->y - rect.y);
 | 
			
		||||
      this->y = rect.y;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Rect::equal(Rect rect) {
 | 
			
		||||
  return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) {  // NOLINT
 | 
			
		||||
  if (!this->is_set()) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  if (absolute) {
 | 
			
		||||
    return ((test_x >= this->x) && (test_x <= this->x2()) && (test_y >= this->y) && (test_y <= this->y2()));
 | 
			
		||||
  } else {
 | 
			
		||||
    return ((test_x >= 0) && (test_x <= this->w) && (test_y >= 0) && (test_y <= this->h));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Rect::inside(Rect rect, bool absolute) {
 | 
			
		||||
  if (!this->is_set() || !rect.is_set()) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  if (absolute) {
 | 
			
		||||
    return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y));
 | 
			
		||||
  } else {
 | 
			
		||||
    return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Rect::info(const std::string &prefix) {
 | 
			
		||||
  if (this->is_set()) {
 | 
			
		||||
    ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d] (%3d,%3d)", prefix.c_str(), this->x, this->y, this->w, this->h, this->x2(),
 | 
			
		||||
             this->y2());
 | 
			
		||||
  } else
 | 
			
		||||
    ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace display
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,36 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display {
 | 
			
		||||
 | 
			
		||||
static const int16_t VALUE_NO_SET = 32766;
 | 
			
		||||
 | 
			
		||||
class Rect {
 | 
			
		||||
 public:
 | 
			
		||||
  int16_t x;  ///< X coordinate of corner
 | 
			
		||||
  int16_t y;  ///< Y coordinate of corner
 | 
			
		||||
  int16_t w;  ///< Width of region
 | 
			
		||||
  int16_t h;  ///< Height of region
 | 
			
		||||
 | 
			
		||||
  Rect() : x(VALUE_NO_SET), y(VALUE_NO_SET), w(VALUE_NO_SET), h(VALUE_NO_SET) {}  // NOLINT
 | 
			
		||||
  inline Rect(int16_t x, int16_t y, int16_t w, int16_t h) ALWAYS_INLINE : x(x), y(y), w(w), h(h) {}
 | 
			
		||||
  inline int16_t x2() { return this->x + this->w; };  ///< X coordinate of corner
 | 
			
		||||
  inline int16_t y2() { return this->y + this->h; };  ///< Y coordinate of corner
 | 
			
		||||
 | 
			
		||||
  inline bool is_set() ALWAYS_INLINE { return (this->h != VALUE_NO_SET) && (this->w != VALUE_NO_SET); }
 | 
			
		||||
 | 
			
		||||
  void expand(int16_t horizontal, int16_t vertical);
 | 
			
		||||
 | 
			
		||||
  void extend(Rect rect);
 | 
			
		||||
  void shrink(Rect rect);
 | 
			
		||||
 | 
			
		||||
  bool inside(Rect rect, bool absolute = true);
 | 
			
		||||
  bool inside(int16_t test_x, int16_t test_y, bool absolute = true);
 | 
			
		||||
  bool equal(Rect rect);
 | 
			
		||||
  void info(const std::string &prefix = "rect info:");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace display
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -37,14 +37,14 @@ void DS1307Component::read_time() {
 | 
			
		||||
    ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  ESPTime rtc_time{.second = uint8_t(ds1307_.reg.second + 10 * ds1307_.reg.second_10),
 | 
			
		||||
                   .minute = uint8_t(ds1307_.reg.minute + 10u * ds1307_.reg.minute_10),
 | 
			
		||||
                   .hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10),
 | 
			
		||||
                   .day_of_week = uint8_t(ds1307_.reg.weekday),
 | 
			
		||||
                   .day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10),
 | 
			
		||||
                   .day_of_year = 1,  // ignored by recalc_timestamp_utc(false)
 | 
			
		||||
                   .month = uint8_t(ds1307_.reg.month + 10u * ds1307_.reg.month_10),
 | 
			
		||||
                   .year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000)};
 | 
			
		||||
  time::ESPTime rtc_time{.second = uint8_t(ds1307_.reg.second + 10 * ds1307_.reg.second_10),
 | 
			
		||||
                         .minute = uint8_t(ds1307_.reg.minute + 10u * ds1307_.reg.minute_10),
 | 
			
		||||
                         .hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10),
 | 
			
		||||
                         .day_of_week = uint8_t(ds1307_.reg.weekday),
 | 
			
		||||
                         .day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10),
 | 
			
		||||
                         .day_of_year = 1,  // ignored by recalc_timestamp_utc(false)
 | 
			
		||||
                         .month = uint8_t(ds1307_.reg.month + 10u * ds1307_.reg.month_10),
 | 
			
		||||
                         .year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000)};
 | 
			
		||||
  rtc_time.recalc_timestamp_utc(false);
 | 
			
		||||
  if (!rtc_time.is_valid()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,6 @@ CONF_CRC_CHECK = "crc_check"
 | 
			
		||||
CONF_DECRYPTION_KEY = "decryption_key"
 | 
			
		||||
CONF_DSMR_ID = "dsmr_id"
 | 
			
		||||
CONF_GAS_MBUS_ID = "gas_mbus_id"
 | 
			
		||||
CONF_WATER_MBUS_ID = "water_mbus_id"
 | 
			
		||||
CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length"
 | 
			
		||||
CONF_REQUEST_INTERVAL = "request_interval"
 | 
			
		||||
CONF_REQUEST_PIN = "request_pin"
 | 
			
		||||
@@ -54,7 +53,6 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
 | 
			
		||||
            cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
 | 
			
		||||
            cv.Optional(CONF_WATER_MBUS_ID, default=2): cv.int_,
 | 
			
		||||
            cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
 | 
			
		||||
            cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
@@ -84,10 +82,9 @@ async def to_code(config):
 | 
			
		||||
    cg.add(var.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT].total_milliseconds))
 | 
			
		||||
 | 
			
		||||
    cg.add_build_flag("-DDSMR_GAS_MBUS_ID=" + str(config[CONF_GAS_MBUS_ID]))
 | 
			
		||||
    cg.add_build_flag("-DDSMR_WATER_MBUS_ID=" + str(config[CONF_WATER_MBUS_ID]))
 | 
			
		||||
 | 
			
		||||
    # DSMR Parser
 | 
			
		||||
    cg.add_library("glmnet/Dsmr", "0.8")
 | 
			
		||||
    cg.add_library("glmnet/Dsmr", "0.5")
 | 
			
		||||
 | 
			
		||||
    # Crypto
 | 
			
		||||
    cg.add_library("rweather/Crypto", "0.4.0")
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user