mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			2023.6.0b4
			...
			jesserockz
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					3792823c24 | 
@@ -4,61 +4,53 @@
 | 
			
		||||
  "postCreateCommand": [
 | 
			
		||||
    "script/devcontainer-post-create"
 | 
			
		||||
  ],
 | 
			
		||||
  "containerEnv": {
 | 
			
		||||
    "DEVCONTAINER": "1"
 | 
			
		||||
  },
 | 
			
		||||
  "runArgs": [
 | 
			
		||||
    "--privileged",
 | 
			
		||||
    "-e",
 | 
			
		||||
    "ESPHOME_DASHBOARD_USE_PING=1"
 | 
			
		||||
  ],
 | 
			
		||||
  "appPort": 6052,
 | 
			
		||||
  "customizations": {
 | 
			
		||||
    "vscode": {
 | 
			
		||||
      "extensions": [
 | 
			
		||||
        // python
 | 
			
		||||
        "ms-python.python",
 | 
			
		||||
        "visualstudioexptteam.vscodeintellicode",
 | 
			
		||||
        // yaml
 | 
			
		||||
        "redhat.vscode-yaml",
 | 
			
		||||
        // cpp
 | 
			
		||||
        "ms-vscode.cpptools",
 | 
			
		||||
        // editorconfig
 | 
			
		||||
        "editorconfig.editorconfig",
 | 
			
		||||
      ],
 | 
			
		||||
      "settings": {
 | 
			
		||||
        "python.languageServer": "Pylance",
 | 
			
		||||
        "python.pythonPath": "/usr/bin/python3",
 | 
			
		||||
        "python.linting.pylintEnabled": true,
 | 
			
		||||
        "python.linting.enabled": true,
 | 
			
		||||
        "python.formatting.provider": "black",
 | 
			
		||||
        "editor.formatOnPaste": false,
 | 
			
		||||
        "editor.formatOnSave": true,
 | 
			
		||||
        "editor.formatOnType": true,
 | 
			
		||||
        "files.trimTrailingWhitespace": true,
 | 
			
		||||
        "terminal.integrated.defaultProfile.linux": "bash",
 | 
			
		||||
        "yaml.customTags": [
 | 
			
		||||
          "!secret scalar",
 | 
			
		||||
          "!lambda scalar",
 | 
			
		||||
          "!extend scalar",
 | 
			
		||||
          "!include_dir_named scalar",
 | 
			
		||||
          "!include_dir_list scalar",
 | 
			
		||||
          "!include_dir_merge_list scalar",
 | 
			
		||||
          "!include_dir_merge_named scalar"
 | 
			
		||||
        ],
 | 
			
		||||
        "files.exclude": {
 | 
			
		||||
          "**/.git": true,
 | 
			
		||||
          "**/.DS_Store": true,
 | 
			
		||||
          "**/*.pyc": {
 | 
			
		||||
            "when": "$(basename).py"
 | 
			
		||||
          },
 | 
			
		||||
          "**/__pycache__": true
 | 
			
		||||
        },
 | 
			
		||||
        "files.associations": {
 | 
			
		||||
          "**/.vscode/*.json": "jsonc"
 | 
			
		||||
        },
 | 
			
		||||
        "C_Cpp.clang_format_path": "/usr/bin/clang-format-13"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  "extensions": [
 | 
			
		||||
    // python
 | 
			
		||||
    "ms-python.python",
 | 
			
		||||
    "visualstudioexptteam.vscodeintellicode",
 | 
			
		||||
    // yaml
 | 
			
		||||
    "redhat.vscode-yaml",
 | 
			
		||||
    // cpp
 | 
			
		||||
    "ms-vscode.cpptools",
 | 
			
		||||
    // editorconfig
 | 
			
		||||
    "editorconfig.editorconfig",
 | 
			
		||||
  ],
 | 
			
		||||
  "settings": {
 | 
			
		||||
    "python.languageServer": "Pylance",
 | 
			
		||||
    "python.pythonPath": "/usr/bin/python3",
 | 
			
		||||
    "python.linting.pylintEnabled": true,
 | 
			
		||||
    "python.linting.enabled": true,
 | 
			
		||||
    "python.formatting.provider": "black",
 | 
			
		||||
    "editor.formatOnPaste": false,
 | 
			
		||||
    "editor.formatOnSave": true,
 | 
			
		||||
    "editor.formatOnType": true,
 | 
			
		||||
    "files.trimTrailingWhitespace": true,
 | 
			
		||||
    "terminal.integrated.defaultProfile.linux": "bash",
 | 
			
		||||
    "yaml.customTags": [
 | 
			
		||||
      "!secret scalar",
 | 
			
		||||
      "!lambda scalar",
 | 
			
		||||
      "!include_dir_named scalar",
 | 
			
		||||
      "!include_dir_list scalar",
 | 
			
		||||
      "!include_dir_merge_list scalar",
 | 
			
		||||
      "!include_dir_merge_named scalar"
 | 
			
		||||
    ],
 | 
			
		||||
    "files.exclude": {
 | 
			
		||||
      "**/.git": true,
 | 
			
		||||
      "**/.DS_Store": true,
 | 
			
		||||
      "**/*.pyc": {
 | 
			
		||||
        "when": "$(basename).py"
 | 
			
		||||
      },
 | 
			
		||||
      "**/__pycache__": true
 | 
			
		||||
    },
 | 
			
		||||
    "files.associations": {
 | 
			
		||||
      "**/.vscode/*.json": "jsonc"
 | 
			
		||||
    },
 | 
			
		||||
    "C_Cpp.clang_format_path": "/usr/bin/clang-format-11",
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,2 @@
 | 
			
		||||
# Normalize line endings to LF in the repository
 | 
			
		||||
* text eol=lf
 | 
			
		||||
*.png binary
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -11,7 +11,6 @@ on:
 | 
			
		||||
      - ".github/workflows/**"
 | 
			
		||||
      - "requirements*.txt"
 | 
			
		||||
      - "platformio.ini"
 | 
			
		||||
      - "script/platformio_install_deps.py"
 | 
			
		||||
 | 
			
		||||
  pull_request:
 | 
			
		||||
    paths:
 | 
			
		||||
@@ -19,17 +18,11 @@ on:
 | 
			
		||||
      - ".github/workflows/**"
 | 
			
		||||
      - "requirements*.txt"
 | 
			
		||||
      - "platformio.ini"
 | 
			
		||||
      - "script/platformio_install_deps.py"
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
  packages: read
 | 
			
		||||
 | 
			
		||||
concurrency:
 | 
			
		||||
  # yamllint disable-line rule:line-length
 | 
			
		||||
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
 | 
			
		||||
  cancel-in-progress: true
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  check-docker:
 | 
			
		||||
    name: Build docker containers
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										399
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										399
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -7,271 +7,55 @@ on:
 | 
			
		||||
    branches: [dev, beta, release]
 | 
			
		||||
 | 
			
		||||
  pull_request:
 | 
			
		||||
  merge_group:
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v3.5.2
 | 
			
		||||
      - name: Set up Python ${{ env.DEFAULT_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 }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
			
		||||
      - 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 virtual environment
 | 
			
		||||
        uses: actions/cache/restore@v3.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          path: venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
			
		||||
      - 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 virtual environment
 | 
			
		||||
        uses: actions/cache/restore@v3.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          path: venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
			
		||||
      - 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 virtual environment
 | 
			
		||||
        uses: actions/cache/restore@v3.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          path: venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
			
		||||
      - 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 virtual environment
 | 
			
		||||
        uses: actions/cache/restore@v3.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          path: venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
			
		||||
      - 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 virtual environment
 | 
			
		||||
        uses: actions/cache/restore@v3.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          path: venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
			
		||||
      - 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 virtual environment
 | 
			
		||||
        uses: actions/cache/restore@v3.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          path: venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
			
		||||
      - 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 virtual environment
 | 
			
		||||
        uses: actions/cache/restore@v3.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          path: venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
			
		||||
      - 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 virtual environment
 | 
			
		||||
        uses: actions/cache/restore@v3.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          path: venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
			
		||||
      - name: Cache platformio
 | 
			
		||||
        uses: actions/cache@v3.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
          key: platformio-test${{ matrix.file }}-${{ hashFiles('platformio.ini') }}
 | 
			
		||||
      - 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
 | 
			
		||||
      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/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: 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
 | 
			
		||||
@@ -296,65 +80,106 @@ 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 virtual environment
 | 
			
		||||
        uses: actions/cache/restore@v3.3.1
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v4
 | 
			
		||||
        id: python
 | 
			
		||||
        with:
 | 
			
		||||
          path: venv
 | 
			
		||||
          python-version: "3.9"
 | 
			
		||||
 | 
			
		||||
      - name: Cache virtualenv
 | 
			
		||||
        uses: actions/cache@v3
 | 
			
		||||
        with:
 | 
			
		||||
          path: .venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
          key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
 | 
			
		||||
        # Use per check platformio cache because checks use different parts
 | 
			
		||||
          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-11 \
 | 
			
		||||
              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.3.0
 | 
			
		||||
 | 
			
		||||
      - name: Suggested changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
        # yamllint disable-line rule:line-length
 | 
			
		||||
        if: always()
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
    if: always()
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Success
 | 
			
		||||
        if: ${{ !(contains(needs.*.result, 'failure')) }}
 | 
			
		||||
        run: exit 0
 | 
			
		||||
      - name: Failure
 | 
			
		||||
        if: ${{ contains(needs.*.result, 'failure') }}
 | 
			
		||||
        run: exit 1
 | 
			
		||||
        if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python')
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,7 +18,7 @@ jobs:
 | 
			
		||||
  lock:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: dessant/lock-threads@v4
 | 
			
		||||
      - uses: dessant/lock-threads@v3
 | 
			
		||||
        with:
 | 
			
		||||
          pr-inactive-days: "1"
 | 
			
		||||
          pr-lock-reason: ""
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										119
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										119
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -30,12 +30,8 @@ jobs:
 | 
			
		||||
            TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
 | 
			
		||||
            today="$(date --utc '+%Y%m%d')"
 | 
			
		||||
            TAG="${TAG}${today}"
 | 
			
		||||
            BRANCH=${GITHUB_REF#refs/heads/}
 | 
			
		||||
            if [[ "$BRANCH" != "dev" ]]; then
 | 
			
		||||
              TAG="${TAG}-${BRANCH}"
 | 
			
		||||
            fi
 | 
			
		||||
          fi
 | 
			
		||||
          echo "tag=${TAG}" >> $GITHUB_OUTPUT
 | 
			
		||||
          echo "::set-output name=tag::${TAG}"
 | 
			
		||||
        # yamllint enable rule:line-length
 | 
			
		||||
 | 
			
		||||
  deploy-pypi:
 | 
			
		||||
@@ -61,30 +57,17 @@ jobs:
 | 
			
		||||
        run: twine upload dist/*
 | 
			
		||||
 | 
			
		||||
  deploy-docker:
 | 
			
		||||
    name: Build and publish ESPHome ${{ matrix.image.title}}
 | 
			
		||||
    name: Build and publish docker containers
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    permissions:
 | 
			
		||||
      contents: read
 | 
			
		||||
      packages: write
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    continue-on-error: ${{ matrix.image.title == 'lint' }}
 | 
			
		||||
    needs: [init]
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        image:
 | 
			
		||||
          - title: "ha-addon"
 | 
			
		||||
            suffix: "hassio"
 | 
			
		||||
            target: "hassio"
 | 
			
		||||
            baseimg: "hassio"
 | 
			
		||||
          - title: "docker"
 | 
			
		||||
            suffix: ""
 | 
			
		||||
            target: "docker"
 | 
			
		||||
            baseimg: "docker"
 | 
			
		||||
          - title: "lint"
 | 
			
		||||
            suffix: "lint"
 | 
			
		||||
            target: "lint"
 | 
			
		||||
            baseimg: "docker"
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        build_type: ["ha-addon", "docker", "lint"]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
@@ -109,47 +92,69 @@ jobs:
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
      - name: Generate short tags
 | 
			
		||||
        id: tags
 | 
			
		||||
        run: |
 | 
			
		||||
          docker/generate_tags.py \
 | 
			
		||||
            --tag "${{ needs.init.outputs.tag }}" \
 | 
			
		||||
            --suffix "${{ matrix.image.suffix }}"
 | 
			
		||||
 | 
			
		||||
      - name: Build and push
 | 
			
		||||
        uses: docker/build-push-action@v4
 | 
			
		||||
        run: |
 | 
			
		||||
          docker/build.py \
 | 
			
		||||
            --tag "${{ needs.init.outputs.tag }}" \
 | 
			
		||||
            --arch "${{ matrix.arch }}" \
 | 
			
		||||
            --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
            build \
 | 
			
		||||
            --push
 | 
			
		||||
 | 
			
		||||
  deploy-docker-manifest:
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    permissions:
 | 
			
		||||
      contents: read
 | 
			
		||||
      packages: write
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [init, deploy-docker]
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        build_type: ["ha-addon", "docker", "lint"]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v4
 | 
			
		||||
        with:
 | 
			
		||||
          context: .
 | 
			
		||||
          file: ./docker/Dockerfile
 | 
			
		||||
          platforms: linux/amd64,linux/arm/v7,linux/arm64
 | 
			
		||||
          target: ${{ matrix.image.target }}
 | 
			
		||||
          push: true
 | 
			
		||||
          # yamllint disable rule:line-length
 | 
			
		||||
          cache-from: type=registry,ref=ghcr.io/${{ steps.tags.outputs.image }}:cache-${{ steps.tags.outputs.channel }}
 | 
			
		||||
          cache-to: type=registry,ref=ghcr.io/${{ steps.tags.outputs.image }}:cache-${{ steps.tags.outputs.channel }},mode=max
 | 
			
		||||
          # yamllint enable rule:line-length
 | 
			
		||||
          tags: ${{ steps.tags.outputs.tags }}
 | 
			
		||||
          build-args: |
 | 
			
		||||
            BASEIMGTYPE=${{ matrix.image.baseimg }}
 | 
			
		||||
            BUILD_VERSION=${{ needs.init.outputs.tag }}
 | 
			
		||||
          python-version: "3.9"
 | 
			
		||||
      - name: Enable experimental manifest support
 | 
			
		||||
        run: |
 | 
			
		||||
          mkdir -p ~/.docker
 | 
			
		||||
          echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
 | 
			
		||||
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
        uses: docker/login-action@v2
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
      - name: Log in to the GitHub container registry
 | 
			
		||||
        uses: docker/login-action@v2
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
      - name: Run manifest
 | 
			
		||||
        run: |
 | 
			
		||||
          docker/build.py \
 | 
			
		||||
            --tag "${{ needs.init.outputs.tag }}" \
 | 
			
		||||
            --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
            manifest
 | 
			
		||||
 | 
			
		||||
  deploy-ha-addon-repo:
 | 
			
		||||
    if: github.repository == 'esphome/esphome' && github.event_name == 'release'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [deploy-docker]
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Trigger Workflow
 | 
			
		||||
        uses: actions/github-script@v6
 | 
			
		||||
        with:
 | 
			
		||||
          github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
 | 
			
		||||
          script: |
 | 
			
		||||
            github.rest.actions.createWorkflowDispatch({
 | 
			
		||||
              owner: "esphome",
 | 
			
		||||
              repo: "home-assistant-addon",
 | 
			
		||||
              workflow_id: "bump-version.yml",
 | 
			
		||||
              ref: "main",
 | 
			
		||||
              inputs: {
 | 
			
		||||
                version: "${{ github.event.release.tag_name }}",
 | 
			
		||||
                content: ${{ toJSON(github.event.release.body) }}
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
      - env:
 | 
			
		||||
          TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
 | 
			
		||||
        # yamllint disable rule:line-length
 | 
			
		||||
        run: |
 | 
			
		||||
          TAG="${GITHUB_REF#refs/tags/}"
 | 
			
		||||
          curl \
 | 
			
		||||
            -u ":$TOKEN" \
 | 
			
		||||
            -X POST \
 | 
			
		||||
            -H "Accept: application/vnd.github.v3+json" \
 | 
			
		||||
            https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \
 | 
			
		||||
            -d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}"
 | 
			
		||||
        # yamllint enable rule:line-length
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,7 +18,7 @@ jobs:
 | 
			
		||||
  stale:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/stale@v8
 | 
			
		||||
      - uses: actions/stale@v6
 | 
			
		||||
        with:
 | 
			
		||||
          days-before-pr-stale: 90
 | 
			
		||||
          days-before-pr-close: 7
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -38,7 +38,7 @@ jobs:
 | 
			
		||||
  close-issues:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/stale@v8
 | 
			
		||||
      - uses: actions/stale@v6
 | 
			
		||||
        with:
 | 
			
		||||
          days-before-pr-stale: -1
 | 
			
		||||
          days-before-pr-close: -1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										60
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,60 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
name: Synchronise Device Classes from Home Assistant
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: '45 6 * * *'
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: write
 | 
			
		||||
  pull-requests: write
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  sync:
 | 
			
		||||
    name: Sync Device Classes
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
 | 
			
		||||
      - name: Checkout Home Assistant
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
        with:
 | 
			
		||||
          repository: home-assistant/core
 | 
			
		||||
          path: lib/home-assistant
 | 
			
		||||
 | 
			
		||||
      - name: Setup Python
 | 
			
		||||
        uses: actions/setup-python@v4
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: 3.11
 | 
			
		||||
 | 
			
		||||
      - name: Install Home Assistant
 | 
			
		||||
        run: |
 | 
			
		||||
          python -m pip install --upgrade pip
 | 
			
		||||
          pip install -e lib/home-assistant
 | 
			
		||||
 | 
			
		||||
      - name: Sync
 | 
			
		||||
        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
 | 
			
		||||
          delete-branch: true
 | 
			
		||||
          title: "Synchronise Device Classes from Home Assistant"
 | 
			
		||||
          body: ${{ steps.pr-template-body.outputs.body }}
 | 
			
		||||
          token: ${{ secrets.DEVICE_CLASS_SYNC_TOKEN }}
 | 
			
		||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -128,5 +128,3 @@ tests/.esphome/
 | 
			
		||||
 | 
			
		||||
sdkconfig.*
 | 
			
		||||
!sdkconfig.defaults
 | 
			
		||||
 | 
			
		||||
.tests/
 | 
			
		||||
@@ -2,16 +2,16 @@
 | 
			
		||||
# See https://pre-commit.com for more information
 | 
			
		||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
			
		||||
repos:
 | 
			
		||||
  - repo: https://github.com/psf/black
 | 
			
		||||
    rev: 23.3.0
 | 
			
		||||
  - repo: https://github.com/ambv/black
 | 
			
		||||
    rev: 22.6.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: black
 | 
			
		||||
        args:
 | 
			
		||||
          - --safe
 | 
			
		||||
          - --quiet
 | 
			
		||||
        files: ^((esphome|script|tests)/.+)?[^/]+\.py$
 | 
			
		||||
  - repo: https://github.com/PyCQA/flake8
 | 
			
		||||
    rev: 6.0.0
 | 
			
		||||
  - repo: https://gitlab.com/pycqa/flake8
 | 
			
		||||
    rev: 4.0.1
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: flake8
 | 
			
		||||
        additional_dependencies:
 | 
			
		||||
@@ -27,7 +27,7 @@ repos:
 | 
			
		||||
          - --branch=release
 | 
			
		||||
          - --branch=beta
 | 
			
		||||
  - repo: https://github.com/asottile/pyupgrade
 | 
			
		||||
    rev: v3.4.0
 | 
			
		||||
    rev: v3.0.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: pyupgrade
 | 
			
		||||
        args: [--py39-plus]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							@@ -2,24 +2,15 @@
 | 
			
		||||
  "version": "2.0.0",
 | 
			
		||||
  "tasks": [
 | 
			
		||||
    {
 | 
			
		||||
      "label": "Run Dashboard",
 | 
			
		||||
      "label": "run",
 | 
			
		||||
      "type": "shell",
 | 
			
		||||
      "command": "${command:python.interpreterPath}",
 | 
			
		||||
      "args": [
 | 
			
		||||
        "-m",
 | 
			
		||||
        "esphome",
 | 
			
		||||
        "dashboard",
 | 
			
		||||
        "config/"
 | 
			
		||||
      ],
 | 
			
		||||
      "command": "python3 -m esphome dashboard config/",
 | 
			
		||||
      "problemMatcher": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "label": "clang-tidy",
 | 
			
		||||
      "type": "shell",
 | 
			
		||||
      "command": "${command:python.interpreterPath}",
 | 
			
		||||
      "args": [
 | 
			
		||||
        "./script/clang-tidy"
 | 
			
		||||
      ],
 | 
			
		||||
      "command": "./script/clang-tidy",
 | 
			
		||||
      "problemMatcher": [
 | 
			
		||||
        {
 | 
			
		||||
          "owner": "clang-tidy",
 | 
			
		||||
@@ -36,24 +27,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": []
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										57
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										57
									
								
								CODEOWNERS
									
									
									
									
									
								
							@@ -11,7 +11,6 @@ esphome/*.py @esphome/core
 | 
			
		||||
esphome/core/* @esphome/core
 | 
			
		||||
 | 
			
		||||
# Integrations
 | 
			
		||||
esphome/components/absolute_humidity/* @DAVe3283
 | 
			
		||||
esphome/components/ac_dimmer/* @glmnet
 | 
			
		||||
esphome/components/adc/* @esphome/core
 | 
			
		||||
esphome/components/adc128s102/* @DeerMaximum
 | 
			
		||||
@@ -19,15 +18,12 @@ esphome/components/addressable_light/* @justfalter
 | 
			
		||||
esphome/components/airthings_ble/* @jeromelaban
 | 
			
		||||
esphome/components/airthings_wave_mini/* @ncareau
 | 
			
		||||
esphome/components/airthings_wave_plus/* @jeromelaban
 | 
			
		||||
esphome/components/alarm_control_panel/* @grahambrown11
 | 
			
		||||
esphome/components/am43/* @buxtronix
 | 
			
		||||
esphome/components/am43/cover/* @buxtronix
 | 
			
		||||
esphome/components/am43/sensor/* @buxtronix
 | 
			
		||||
esphome/components/analog_threshold/* @ianchi
 | 
			
		||||
esphome/components/animation/* @syndlex
 | 
			
		||||
esphome/components/anova/* @buxtronix
 | 
			
		||||
esphome/components/api/* @OttoWinter
 | 
			
		||||
esphome/components/as7341/* @mrgnr
 | 
			
		||||
esphome/components/async_tcp/* @OttoWinter
 | 
			
		||||
esphome/components/atc_mithermometer/* @ahpohl
 | 
			
		||||
esphome/components/b_parasite/* @rbaron
 | 
			
		||||
@@ -45,8 +41,6 @@ esphome/components/ble_client/* @buxtronix
 | 
			
		||||
esphome/components/bluetooth_proxy/* @jesserockz
 | 
			
		||||
esphome/components/bme680_bsec/* @trvrnrth
 | 
			
		||||
esphome/components/bmp3xx/* @martgras
 | 
			
		||||
esphome/components/bp1658cj/* @Cossid
 | 
			
		||||
esphome/components/bp5758d/* @Cossid
 | 
			
		||||
esphome/components/button/* @esphome/core
 | 
			
		||||
esphome/components/canbus/* @danielschramm @mvturnho
 | 
			
		||||
esphome/components/cap1188/* @MrEditor97
 | 
			
		||||
@@ -71,11 +65,9 @@ esphome/components/debug/* @OttoWinter
 | 
			
		||||
esphome/components/delonghi/* @grob6000
 | 
			
		||||
esphome/components/dfplayer/* @glmnet
 | 
			
		||||
esphome/components/dht/* @OttoWinter
 | 
			
		||||
esphome/components/display_menu_base/* @numo68
 | 
			
		||||
esphome/components/dps310/* @kbx81
 | 
			
		||||
esphome/components/ds1307/* @badbadc0ffee
 | 
			
		||||
esphome/components/dsmr/* @glmnet @zuidwijk
 | 
			
		||||
esphome/components/ee895/* @Stock-M
 | 
			
		||||
esphome/components/ektf2232/* @jesserockz
 | 
			
		||||
esphome/components/ens210/* @itn3rd77
 | 
			
		||||
esphome/components/esp32/* @esphome/core
 | 
			
		||||
@@ -85,7 +77,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
 | 
			
		||||
@@ -95,58 +86,38 @@ esphome/components/factory_reset/* @anatoly-savchenkov
 | 
			
		||||
esphome/components/fastled_base/* @OttoWinter
 | 
			
		||||
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/growatt_solar/* @leeuwte
 | 
			
		||||
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
 | 
			
		||||
esphome/components/hyt271/* @Philippe12
 | 
			
		||||
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
 | 
			
		||||
esphome/components/ina260/* @MrEditor97
 | 
			
		||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
 | 
			
		||||
esphome/components/inkplate6/* @jesserockz
 | 
			
		||||
esphome/components/integration/* @OttoWinter
 | 
			
		||||
esphome/components/internal_temperature/* @Mat931
 | 
			
		||||
esphome/components/interval/* @esphome/core
 | 
			
		||||
esphome/components/json/* @OttoWinter
 | 
			
		||||
esphome/components/kalman_combinator/* @Cat-Ion
 | 
			
		||||
esphome/components/key_collector/* @ssieb
 | 
			
		||||
esphome/components/key_provider/* @ssieb
 | 
			
		||||
esphome/components/kuntze/* @ssieb
 | 
			
		||||
esphome/components/lcd_menu/* @numo68
 | 
			
		||||
esphome/components/ld2410/* @sebcaps
 | 
			
		||||
esphome/components/ledc/* @OttoWinter
 | 
			
		||||
esphome/components/light/* @esphome/core
 | 
			
		||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
 | 
			
		||||
esphome/components/lock/* @esphome/core
 | 
			
		||||
esphome/components/logger/* @esphome/core
 | 
			
		||||
esphome/components/ltr390/* @sjtrny
 | 
			
		||||
esphome/components/matrix_keypad/* @ssieb
 | 
			
		||||
esphome/components/max31865/* @DAVe3283
 | 
			
		||||
esphome/components/max44009/* @berfenger
 | 
			
		||||
esphome/components/max6956/* @looping40
 | 
			
		||||
esphome/components/max7219digit/* @rspaargaren
 | 
			
		||||
esphome/components/max9611/* @mckaymatthew
 | 
			
		||||
esphome/components/mcp23008/* @jesserockz
 | 
			
		||||
@@ -165,14 +136,10 @@ esphome/components/mcp9808/* @k7hpn
 | 
			
		||||
esphome/components/md5/* @esphome/core
 | 
			
		||||
esphome/components/mdns/* @esphome/core
 | 
			
		||||
esphome/components/media_player/* @jesserockz
 | 
			
		||||
esphome/components/microphone/* @jesserockz
 | 
			
		||||
esphome/components/mics_4514/* @jesserockz
 | 
			
		||||
esphome/components/midea/* @dudanov
 | 
			
		||||
esphome/components/midea_ir/* @dudanov
 | 
			
		||||
esphome/components/mitsubishi/* @RubyBailey
 | 
			
		||||
esphome/components/mlx90393/* @functionpointer
 | 
			
		||||
esphome/components/mlx90614/* @jesserockz
 | 
			
		||||
esphome/components/mmc5603/* @benhoff
 | 
			
		||||
esphome/components/modbus_controller/* @martgras
 | 
			
		||||
esphome/components/modbus_controller/binary_sensor/* @martgras
 | 
			
		||||
esphome/components/modbus_controller/number/* @martgras
 | 
			
		||||
@@ -181,9 +148,8 @@ esphome/components/modbus_controller/select/* @martgras @stegm
 | 
			
		||||
esphome/components/modbus_controller/sensor/* @martgras
 | 
			
		||||
esphome/components/modbus_controller/switch/* @martgras
 | 
			
		||||
esphome/components/modbus_controller/text_sensor/* @martgras
 | 
			
		||||
esphome/components/mopeka_ble/* @Fabian-Schmidt @spbrogan
 | 
			
		||||
esphome/components/mopeka_ble/* @spbrogan
 | 
			
		||||
esphome/components/mopeka_pro_check/* @spbrogan
 | 
			
		||||
esphome/components/mopeka_std_check/* @Fabian-Schmidt
 | 
			
		||||
esphome/components/mpl3115a2/* @kbickar
 | 
			
		||||
esphome/components/mpu6886/* @fabaff
 | 
			
		||||
esphome/components/network/* @esphome/core
 | 
			
		||||
@@ -196,9 +162,6 @@ esphome/components/nfc/* @jesserockz
 | 
			
		||||
esphome/components/number/* @esphome/core
 | 
			
		||||
esphome/components/ota/* @esphome/core
 | 
			
		||||
esphome/components/output/* @esphome/core
 | 
			
		||||
esphome/components/pca6416a/* @Mat931
 | 
			
		||||
esphome/components/pca9554/* @hwstar
 | 
			
		||||
esphome/components/pcf85063/* @brogon
 | 
			
		||||
esphome/components/pid/* @OttoWinter
 | 
			
		||||
esphome/components/pipsolar/* @andreashergert1984
 | 
			
		||||
esphome/components/pm1006/* @habbie
 | 
			
		||||
@@ -222,7 +185,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
 | 
			
		||||
@@ -232,7 +194,6 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces
 | 
			
		||||
esphome/components/sdp3x/* @Azimath
 | 
			
		||||
esphome/components/selec_meter/* @sourabhjaiswal
 | 
			
		||||
esphome/components/select/* @esphome/core
 | 
			
		||||
esphome/components/sen21231/* @shreyaskarnik
 | 
			
		||||
esphome/components/sen5x/* @martgras
 | 
			
		||||
esphome/components/sensirion_common/* @martgras
 | 
			
		||||
esphome/components/sensor/* @esphome/core
 | 
			
		||||
@@ -241,18 +202,12 @@ esphome/components/sgp4x/* @SenexCrenshaw @martgras
 | 
			
		||||
esphome/components/shelly_dimmer/* @edge90 @rnauber
 | 
			
		||||
esphome/components/sht4x/* @sjtrny
 | 
			
		||||
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/sm2235/* @Cossid
 | 
			
		||||
esphome/components/sm2335/* @Cossid
 | 
			
		||||
esphome/components/sm2135/* @BoukeHaarsma23
 | 
			
		||||
esphome/components/sml/* @alengwenus
 | 
			
		||||
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
 | 
			
		||||
@@ -276,18 +231,14 @@ esphome/components/switch/* @esphome/core
 | 
			
		||||
esphome/components/t6615/* @tylermenezes
 | 
			
		||||
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
 | 
			
		||||
@@ -304,16 +255,12 @@ esphome/components/uart/* @esphome/core
 | 
			
		||||
esphome/components/ufire_ec/* @pvizeli
 | 
			
		||||
esphome/components/ufire_ise/* @pvizeli
 | 
			
		||||
esphome/components/ultrasonic/* @OttoWinter
 | 
			
		||||
esphome/components/vbus/* @ssieb
 | 
			
		||||
esphome/components/version/* @esphome/core
 | 
			
		||||
esphome/components/voice_assistant/* @jesserockz
 | 
			
		||||
esphome/components/wake_on_lan/* @willwill2will54
 | 
			
		||||
esphome/components/web_server_base/* @OttoWinter
 | 
			
		||||
esphome/components/whirlpool/* @glmnet
 | 
			
		||||
esphome/components/whynter/* @aeonsablaze
 | 
			
		||||
esphome/components/wiegand/* @ssieb
 | 
			
		||||
esphome/components/wl_134/* @hobbypunk90
 | 
			
		||||
esphome/components/x9c/* @EtienneMD
 | 
			
		||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
 | 
			
		||||
esphome/components/xiaomi_mhoc303/* @drug123
 | 
			
		||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
include LICENSE
 | 
			
		||||
include README.md
 | 
			
		||||
include requirements.txt
 | 
			
		||||
recursive-include esphome *.cpp *.h *.tcc *.c
 | 
			
		||||
include esphome/dashboard/templates/*.html
 | 
			
		||||
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
 | 
			
		||||
recursive-include esphome *.cpp *.h *.tcc
 | 
			
		||||
recursive-include esphome *.py.script
 | 
			
		||||
recursive-include esphome LICENSE.txt
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,17 @@
 | 
			
		||||
ARG BASEIMGTYPE=docker
 | 
			
		||||
 | 
			
		||||
# https://github.com/hassio-addons/addon-debian-base/releases
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base:6.2.3 AS base-hassio
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.3.0 AS base-hassio-amd64
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.3.0 AS base-hassio-arm64
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.3.0 AS base-hassio-armv7
 | 
			
		||||
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
 | 
			
		||||
FROM debian:bullseye-20230208-slim AS base-docker
 | 
			
		||||
FROM debian:bullseye-20220328-slim AS base-docker-amd64
 | 
			
		||||
FROM debian:bullseye-20220328-slim AS base-docker-arm64
 | 
			
		||||
FROM debian:bullseye-20220328-slim AS base-docker-armv7
 | 
			
		||||
 | 
			
		||||
FROM base-${BASEIMGTYPE} AS base
 | 
			
		||||
 | 
			
		||||
ARG TARGETARCH
 | 
			
		||||
ARG TARGETVARIANT
 | 
			
		||||
# Use TARGETARCH/TARGETVARIANT defined by docker
 | 
			
		||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
 | 
			
		||||
FROM base-${BASEIMGTYPE}-${TARGETARCH}${TARGETVARIANT} AS base
 | 
			
		||||
 | 
			
		||||
RUN \
 | 
			
		||||
    apt-get update \
 | 
			
		||||
@@ -24,13 +27,10 @@ RUN \
 | 
			
		||||
        python3-setuptools=52.0.0-4 \
 | 
			
		||||
        python3-pil=8.1.2+dfsg-0.3+deb11u1 \
 | 
			
		||||
        python3-cryptography=3.3.2-1 \
 | 
			
		||||
        python3-venv=3.9.2-3 \
 | 
			
		||||
        iputils-ping=3:20210202-1 \
 | 
			
		||||
        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 \
 | 
			
		||||
        git=1:2.30.2-1 \
 | 
			
		||||
        curl=7.74.0-1.3+deb11u1 \
 | 
			
		||||
        openssh-client=1:8.4p1-5 \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
        /tmp/* \
 | 
			
		||||
        /var/{cache,log}/* \
 | 
			
		||||
@@ -42,19 +42,11 @@ ENV \
 | 
			
		||||
  # Store globally installed pio libs in /piolibs
 | 
			
		||||
  PLATFORMIO_GLOBALLIB_DIR=/piolibs
 | 
			
		||||
 | 
			
		||||
# Support legacy binaries on Debian multiarch system. There is no "correct" way
 | 
			
		||||
# to do this, other than using properly built toolchains...
 | 
			
		||||
# See: https://unix.stackexchange.com/questions/553743/correct-way-to-add-lib-ld-linux-so-3-in-debian
 | 
			
		||||
RUN \
 | 
			
		||||
    if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
 | 
			
		||||
        ln -s /lib/arm-linux-gnueabihf/ld-linux.so.3 /lib/ld-linux.so.3; \
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
RUN \
 | 
			
		||||
    # Ubuntu python3-pip is missing wheel
 | 
			
		||||
    pip3 install --no-cache-dir \
 | 
			
		||||
        wheel==0.37.1 \
 | 
			
		||||
        platformio==6.1.7 \
 | 
			
		||||
        platformio==6.1.4 \
 | 
			
		||||
    # Change some platformio settings
 | 
			
		||||
    && platformio settings set enable_telemetry No \
 | 
			
		||||
    && platformio settings set check_platformio_interval 1000000 \
 | 
			
		||||
@@ -62,10 +54,10 @@ RUN \
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# First install requirements to leverage caching when requirements don't change
 | 
			
		||||
COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini /
 | 
			
		||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
 | 
			
		||||
RUN \
 | 
			
		||||
    pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
 | 
			
		||||
    && /platformio_install_deps.py /platformio.ini --libraries
 | 
			
		||||
    && /platformio_install_deps.py /platformio.ini
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ======================= docker-type image =======================
 | 
			
		||||
@@ -102,7 +94,7 @@ RUN \
 | 
			
		||||
    apt-get update \
 | 
			
		||||
    # Use pinned versions so that we get updates with build caching
 | 
			
		||||
    && apt-get install -y --no-install-recommends \
 | 
			
		||||
        nginx-light=1.18.0-6.1+deb11u3 \
 | 
			
		||||
        nginx-light=1.18.0-6.1+deb11u2 \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
        /tmp/* \
 | 
			
		||||
        /var/{cache,log}/* \
 | 
			
		||||
@@ -138,11 +130,11 @@ RUN \
 | 
			
		||||
    apt-get update \
 | 
			
		||||
    # Use pinned versions so that we get updates with build caching
 | 
			
		||||
    && apt-get install -y --no-install-recommends \
 | 
			
		||||
        clang-format-13=1:13.0.1-6~deb11u1 \
 | 
			
		||||
        clang-format-11=1:11.0.1-2 \
 | 
			
		||||
        clang-tidy-11=1:11.0.1-2 \
 | 
			
		||||
        patch=2.7.6-7 \
 | 
			
		||||
        software-properties-common=0.96.20.2-2.1 \
 | 
			
		||||
        nano=5.4-2+deb11u2 \
 | 
			
		||||
        nano=5.4-2+deb11u1 \
 | 
			
		||||
        build-essential=12.9 \
 | 
			
		||||
        python3-dev=3.9.2-3 \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
 
 | 
			
		||||
@@ -8,49 +8,32 @@ import re
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CHANNEL_DEV = "dev"
 | 
			
		||||
CHANNEL_BETA = "beta"
 | 
			
		||||
CHANNEL_RELEASE = "release"
 | 
			
		||||
CHANNEL_DEV = 'dev'
 | 
			
		||||
CHANNEL_BETA = 'beta'
 | 
			
		||||
CHANNEL_RELEASE = 'release'
 | 
			
		||||
CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
 | 
			
		||||
 | 
			
		||||
ARCH_AMD64 = "amd64"
 | 
			
		||||
ARCH_ARMV7 = "armv7"
 | 
			
		||||
ARCH_AARCH64 = "aarch64"
 | 
			
		||||
ARCH_AMD64 = 'amd64'
 | 
			
		||||
ARCH_ARMV7 = 'armv7'
 | 
			
		||||
ARCH_AARCH64 = 'aarch64'
 | 
			
		||||
ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64]
 | 
			
		||||
 | 
			
		||||
TYPE_DOCKER = "docker"
 | 
			
		||||
TYPE_HA_ADDON = "ha-addon"
 | 
			
		||||
TYPE_LINT = "lint"
 | 
			
		||||
TYPE_DOCKER = 'docker'
 | 
			
		||||
TYPE_HA_ADDON = 'ha-addon'
 | 
			
		||||
TYPE_LINT = 'lint'
 | 
			
		||||
TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
parser = argparse.ArgumentParser()
 | 
			
		||||
parser.add_argument(
 | 
			
		||||
    "--tag",
 | 
			
		||||
    type=str,
 | 
			
		||||
    required=True,
 | 
			
		||||
    help="The main docker tag to push to. If a version number also adds latest and/or beta tag",
 | 
			
		||||
)
 | 
			
		||||
parser.add_argument(
 | 
			
		||||
    "--arch", choices=ARCHS, required=False, help="The architecture to build for"
 | 
			
		||||
)
 | 
			
		||||
parser.add_argument(
 | 
			
		||||
    "--build-type", choices=TYPES, required=True, help="The type of build to run"
 | 
			
		||||
)
 | 
			
		||||
parser.add_argument(
 | 
			
		||||
    "--dry-run", action="store_true", help="Don't run any commands, just print them"
 | 
			
		||||
)
 | 
			
		||||
subparsers = parser.add_subparsers(
 | 
			
		||||
    help="Action to perform", dest="command", required=True
 | 
			
		||||
)
 | 
			
		||||
parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag")
 | 
			
		||||
parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for")
 | 
			
		||||
parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run")
 | 
			
		||||
parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them")
 | 
			
		||||
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
 | 
			
		||||
build_parser = subparsers.add_parser("build", help="Build the image")
 | 
			
		||||
build_parser.add_argument("--push", help="Also push the images", action="store_true")
 | 
			
		||||
build_parser.add_argument(
 | 
			
		||||
    "--load", help="Load the docker image locally", action="store_true"
 | 
			
		||||
)
 | 
			
		||||
manifest_parser = subparsers.add_parser(
 | 
			
		||||
    "manifest", help="Create a manifest from already pushed images"
 | 
			
		||||
)
 | 
			
		||||
build_parser.add_argument("--load", help="Load the docker image locally", action="store_true")
 | 
			
		||||
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dataclass(frozen=True)
 | 
			
		||||
@@ -66,7 +49,7 @@ class DockerParams:
 | 
			
		||||
        prefix = {
 | 
			
		||||
            TYPE_DOCKER: "esphome/esphome",
 | 
			
		||||
            TYPE_HA_ADDON: "esphome/esphome-hassio",
 | 
			
		||||
            TYPE_LINT: "esphome/esphome-lint",
 | 
			
		||||
            TYPE_LINT: "esphome/esphome-lint"
 | 
			
		||||
        }[build_type]
 | 
			
		||||
        build_to = f"{prefix}-{arch}"
 | 
			
		||||
        baseimgtype = {
 | 
			
		||||
@@ -145,21 +128,13 @@ def main():
 | 
			
		||||
 | 
			
		||||
        # 3. build
 | 
			
		||||
        cmd = [
 | 
			
		||||
            "docker",
 | 
			
		||||
            "buildx",
 | 
			
		||||
            "build",
 | 
			
		||||
            "--build-arg",
 | 
			
		||||
            f"BASEIMGTYPE={params.baseimgtype}",
 | 
			
		||||
            "--build-arg",
 | 
			
		||||
            f"BUILD_VERSION={args.tag}",
 | 
			
		||||
            "--cache-from",
 | 
			
		||||
            f"type=registry,ref={cache_img}",
 | 
			
		||||
            "--file",
 | 
			
		||||
            "docker/Dockerfile",
 | 
			
		||||
            "--platform",
 | 
			
		||||
            params.platform,
 | 
			
		||||
            "--target",
 | 
			
		||||
            params.target,
 | 
			
		||||
            "docker", "buildx", "build",
 | 
			
		||||
            "--build-arg", f"BASEIMGTYPE={params.baseimgtype}",
 | 
			
		||||
            "--build-arg", f"BUILD_VERSION={args.tag}",
 | 
			
		||||
            "--cache-from", f"type=registry,ref={cache_img}",
 | 
			
		||||
            "--file", "docker/Dockerfile",
 | 
			
		||||
            "--platform", params.platform,
 | 
			
		||||
            "--target", params.target,
 | 
			
		||||
        ]
 | 
			
		||||
        for img in imgs:
 | 
			
		||||
            cmd += ["--tag", img]
 | 
			
		||||
@@ -185,7 +160,9 @@ def main():
 | 
			
		||||
            run_command(*cmd)
 | 
			
		||||
        # 2. Push manifests
 | 
			
		||||
        for target in targets:
 | 
			
		||||
            run_command("docker", "manifest", "push", target)
 | 
			
		||||
            run_command(
 | 
			
		||||
                "docker", "manifest", "push", target
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
 
 | 
			
		||||
@@ -1,68 +0,0 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
import re
 | 
			
		||||
import os
 | 
			
		||||
import argparse
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
CHANNEL_DEV = "dev"
 | 
			
		||||
CHANNEL_BETA = "beta"
 | 
			
		||||
CHANNEL_RELEASE = "release"
 | 
			
		||||
 | 
			
		||||
parser = argparse.ArgumentParser()
 | 
			
		||||
parser.add_argument(
 | 
			
		||||
    "--tag",
 | 
			
		||||
    type=str,
 | 
			
		||||
    required=True,
 | 
			
		||||
    help="The main docker tag to push to. If a version number also adds latest and/or beta tag",
 | 
			
		||||
)
 | 
			
		||||
parser.add_argument(
 | 
			
		||||
    "--suffix",
 | 
			
		||||
    type=str,
 | 
			
		||||
    required=True,
 | 
			
		||||
    help="The suffix of the tag.",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    # detect channel from tag
 | 
			
		||||
    match = re.match(r"^(\d+\.\d+)(?:\.\d+)?(b\d+)?$", args.tag)
 | 
			
		||||
    major_minor_version = None
 | 
			
		||||
    if match is None:
 | 
			
		||||
        channel = CHANNEL_DEV
 | 
			
		||||
    elif match.group(2) is None:
 | 
			
		||||
        major_minor_version = match.group(1)
 | 
			
		||||
        channel = CHANNEL_RELEASE
 | 
			
		||||
    else:
 | 
			
		||||
        channel = CHANNEL_BETA
 | 
			
		||||
 | 
			
		||||
    tags_to_push = [args.tag]
 | 
			
		||||
    if channel == CHANNEL_DEV:
 | 
			
		||||
        tags_to_push.append("dev")
 | 
			
		||||
    elif channel == CHANNEL_BETA:
 | 
			
		||||
        tags_to_push.append("beta")
 | 
			
		||||
    elif channel == CHANNEL_RELEASE:
 | 
			
		||||
        # Additionally push to beta
 | 
			
		||||
        tags_to_push.append("beta")
 | 
			
		||||
        tags_to_push.append("latest")
 | 
			
		||||
 | 
			
		||||
        if major_minor_version:
 | 
			
		||||
            tags_to_push.append("stable")
 | 
			
		||||
            tags_to_push.append(major_minor_version)
 | 
			
		||||
 | 
			
		||||
    suffix = f"-{args.suffix}" if args.suffix else ""
 | 
			
		||||
 | 
			
		||||
    with open(os.environ["GITHUB_OUTPUT"], "w") as f:
 | 
			
		||||
        print(f"channel={channel}", file=f)
 | 
			
		||||
        print(f"image=esphome/esphome{suffix}", file=f)
 | 
			
		||||
        full_tags = []
 | 
			
		||||
 | 
			
		||||
        for tag in tags_to_push:
 | 
			
		||||
            full_tags += [f"ghcr.io/esphome/esphome{suffix}:{tag}"]
 | 
			
		||||
            full_tags += [f"esphome/esphome{suffix}:{tag}"]
 | 
			
		||||
        print(f"tags={','.join(full_tags)}", file=f)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										41
									
								
								docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										41
									
								
								docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
#!/usr/bin/with-contenv bashio
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
# Community Hass.io Add-ons: ESPHome
 | 
			
		||||
# This files check if all user configuration requirements are met
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
 | 
			
		||||
# Check SSL requirements, if enabled
 | 
			
		||||
if bashio::config.true 'ssl'; then
 | 
			
		||||
    if ! bashio::config.has_value 'certfile'; then
 | 
			
		||||
        bashio::log.fatal 'SSL is enabled, but no certfile was specified.'
 | 
			
		||||
        bashio::exit.nok
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    if ! bashio::config.has_value 'keyfile'; then
 | 
			
		||||
        bashio::log.fatal 'SSL is enabled, but no keyfile was specified'
 | 
			
		||||
        bashio::exit.nok
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    certfile="/ssl/$(bashio::config 'certfile')"
 | 
			
		||||
    keyfile="/ssl/$(bashio::config 'keyfile')"
 | 
			
		||||
 | 
			
		||||
    if ! bashio::fs.file_exists "${certfile}"; then
 | 
			
		||||
        if ! bashio::fs.file_exists "${keyfile}"; then
 | 
			
		||||
            # Both files are missing, let's print a friendlier error message
 | 
			
		||||
            bashio::log.fatal 'You enabled encrypted connections using the "ssl": true option.'
 | 
			
		||||
            bashio::log.fatal "However, the SSL files '${certfile}' and '${keyfile}'"
 | 
			
		||||
            bashio::log.fatal "were not found. If you're using Hass.io on your local network and don't want"
 | 
			
		||||
            bashio::log.fatal 'to encrypt connections to the ESPHome dashboard, you can manually disable'
 | 
			
		||||
            bashio::log.fatal 'SSL by setting "ssl" to false."'
 | 
			
		||||
            bashio::exit.nok
 | 
			
		||||
        fi
 | 
			
		||||
        bashio::log.fatal "The configured certfile '${certfile}' was not found."
 | 
			
		||||
        bashio::exit.nok
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    if ! bashio::fs.file_exists "/ssl/$(bashio::config 'keyfile')"; then
 | 
			
		||||
        bashio::log.fatal "The configured keyfile '${keyfile}' was not found."
 | 
			
		||||
        bashio::exit.nok
 | 
			
		||||
    fi
 | 
			
		||||
fi
 | 
			
		||||
							
								
								
									
										34
									
								
								docker/ha-addon-rootfs/etc/cont-init.d/20-nginx.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										34
									
								
								docker/ha-addon-rootfs/etc/cont-init.d/20-nginx.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
#!/usr/bin/with-contenv bashio
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
# Community Hass.io Add-ons: ESPHome
 | 
			
		||||
# Configures NGINX for use with ESPHome
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
 | 
			
		||||
declare certfile
 | 
			
		||||
declare keyfile
 | 
			
		||||
declare direct_port
 | 
			
		||||
declare ingress_interface
 | 
			
		||||
declare ingress_port
 | 
			
		||||
 | 
			
		||||
mkdir -p /var/log/nginx
 | 
			
		||||
 | 
			
		||||
direct_port=$(bashio::addon.port 6052)
 | 
			
		||||
if bashio::var.has_value "${direct_port}"; then
 | 
			
		||||
    if bashio::config.true 'ssl'; then
 | 
			
		||||
        certfile=$(bashio::config 'certfile')
 | 
			
		||||
        keyfile=$(bashio::config 'keyfile')
 | 
			
		||||
 | 
			
		||||
        mv /etc/nginx/servers/direct-ssl.disabled /etc/nginx/servers/direct.conf
 | 
			
		||||
        sed -i "s/%%certfile%%/${certfile}/g" /etc/nginx/servers/direct.conf
 | 
			
		||||
        sed -i "s/%%keyfile%%/${keyfile}/g" /etc/nginx/servers/direct.conf
 | 
			
		||||
    else
 | 
			
		||||
        mv /etc/nginx/servers/direct.disabled /etc/nginx/servers/direct.conf
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    sed -i "s/%%port%%/${direct_port}/g" /etc/nginx/servers/direct.conf
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
ingress_port=$(bashio::addon.ingress_port)
 | 
			
		||||
ingress_interface=$(bashio::addon.ip_address)
 | 
			
		||||
sed -i "s/%%port%%/${ingress_port}/g" /etc/nginx/servers/ingress.conf
 | 
			
		||||
sed -i "s/%%interface%%/${ingress_interface}/g" /etc/nginx/servers/ingress.conf
 | 
			
		||||
							
								
								
									
										9
									
								
								docker/ha-addon-rootfs/etc/cont-init.d/30-dirs.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								docker/ha-addon-rootfs/etc/cont-init.d/30-dirs.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
#!/usr/bin/with-contenv bashio
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
# Community Hass.io Add-ons: ESPHome
 | 
			
		||||
# This files creates all directories used by esphome
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
 | 
			
		||||
pio_cache_base=/data/cache/platformio
 | 
			
		||||
 | 
			
		||||
mkdir -p "${pio_cache_base}"
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
proxy_http_version          1.1;
 | 
			
		||||
proxy_ignore_client_abort   off;
 | 
			
		||||
proxy_read_timeout          86400s;
 | 
			
		||||
proxy_redirect              off;
 | 
			
		||||
proxy_send_timeout          86400s;
 | 
			
		||||
proxy_max_temp_file_size    0;
 | 
			
		||||
proxy_http_version 1.1;
 | 
			
		||||
proxy_ignore_client_abort off;
 | 
			
		||||
proxy_read_timeout 86400s;
 | 
			
		||||
proxy_redirect off;
 | 
			
		||||
proxy_send_timeout 86400s;
 | 
			
		||||
proxy_max_temp_file_size 0;
 | 
			
		||||
 | 
			
		||||
proxy_set_header Accept-Encoding "";
 | 
			
		||||
proxy_set_header Connection $connection_upgrade;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
root            /dev/null;
 | 
			
		||||
server_name     $hostname;
 | 
			
		||||
 | 
			
		||||
client_max_body_size 512m;
 | 
			
		||||
root /dev/null;
 | 
			
		||||
server_name $hostname;
 | 
			
		||||
 | 
			
		||||
add_header X-Content-Type-Options nosniff;
 | 
			
		||||
add_header X-XSS-Protection "1; mode=block";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
ssl_protocols TLSv1.2 TLSv1.3;
 | 
			
		||||
ssl_prefer_server_ciphers off;
 | 
			
		||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
 | 
			
		||||
ssl_protocols TLSv1.2;
 | 
			
		||||
ssl_prefer_server_ciphers on;
 | 
			
		||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA;
 | 
			
		||||
ssl_ecdh_curve secp384r1;
 | 
			
		||||
ssl_session_timeout  10m;
 | 
			
		||||
ssl_session_cache shared:SSL:10m;
 | 
			
		||||
ssl_session_tickets off;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
upstream esphome {
 | 
			
		||||
    server unix:/var/run/esphome.sock;
 | 
			
		||||
}
 | 
			
		||||
@@ -2,6 +2,7 @@ daemon off;
 | 
			
		||||
user root;
 | 
			
		||||
pid /var/run/nginx.pid;
 | 
			
		||||
worker_processes 1;
 | 
			
		||||
# Hass.io addon log
 | 
			
		||||
error_log /proc/1/fd/1 error;
 | 
			
		||||
events {
 | 
			
		||||
    worker_connections 1024;
 | 
			
		||||
@@ -9,22 +10,24 @@ events {
 | 
			
		||||
 | 
			
		||||
http {
 | 
			
		||||
    include /etc/nginx/includes/mime.types;
 | 
			
		||||
 | 
			
		||||
    access_log              off;
 | 
			
		||||
    default_type            application/octet-stream;
 | 
			
		||||
    gzip                    on;
 | 
			
		||||
    keepalive_timeout       65;
 | 
			
		||||
    sendfile                on;
 | 
			
		||||
    server_tokens           off;
 | 
			
		||||
 | 
			
		||||
    tcp_nodelay             on;
 | 
			
		||||
    tcp_nopush              on;
 | 
			
		||||
    access_log stdout;
 | 
			
		||||
    default_type application/octet-stream;
 | 
			
		||||
    gzip on;
 | 
			
		||||
    keepalive_timeout 65;
 | 
			
		||||
    sendfile on;
 | 
			
		||||
    server_tokens off;
 | 
			
		||||
 | 
			
		||||
    map $http_upgrade $connection_upgrade {
 | 
			
		||||
        default upgrade;
 | 
			
		||||
        ''      close;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    include /etc/nginx/includes/upstream.conf;
 | 
			
		||||
    # Use Hass.io supervisor as resolver
 | 
			
		||||
    resolver 172.30.32.2;
 | 
			
		||||
 | 
			
		||||
    upstream esphome {
 | 
			
		||||
        server unix:/var/run/esphome.sock;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    include /etc/nginx/servers/*.conf;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
Without requirements or design, programming is the art of adding bugs to an empty text file. (Louis Srygley)
 | 
			
		||||
@@ -1,26 +1,20 @@
 | 
			
		||||
server {
 | 
			
		||||
    {{ if not .ssl }}
 | 
			
		||||
    listen 6052 default_server;
 | 
			
		||||
    {{ else }}
 | 
			
		||||
    listen 6052 default_server ssl http2;
 | 
			
		||||
    {{ end }}
 | 
			
		||||
    listen %%port%% default_server ssl http2;
 | 
			
		||||
 | 
			
		||||
    include /etc/nginx/includes/server_params.conf;
 | 
			
		||||
    include /etc/nginx/includes/proxy_params.conf;
 | 
			
		||||
 | 
			
		||||
    {{ if .ssl }}
 | 
			
		||||
    include /etc/nginx/includes/ssl_params.conf;
 | 
			
		||||
 | 
			
		||||
    ssl_certificate /ssl/{{ .certfile }};
 | 
			
		||||
    ssl_certificate_key /ssl/{{ .keyfile }};
 | 
			
		||||
    ssl on;
 | 
			
		||||
    ssl_certificate /ssl/%%certfile%%;
 | 
			
		||||
    ssl_certificate_key /ssl/%%keyfile%%;
 | 
			
		||||
 | 
			
		||||
    # Clear Hass.io Ingress header
 | 
			
		||||
    proxy_set_header X-HA-Ingress "";
 | 
			
		||||
 | 
			
		||||
    # Redirect http requests to https on the same port.
 | 
			
		||||
    # https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/
 | 
			
		||||
    error_page 497 https://$http_host$request_uri;
 | 
			
		||||
    {{ end }}
 | 
			
		||||
 | 
			
		||||
    # Clear Home Assistant Ingress header
 | 
			
		||||
    proxy_set_header X-HA-Ingress "";
 | 
			
		||||
 | 
			
		||||
    location / {
 | 
			
		||||
        proxy_pass http://esphome;
 | 
			
		||||
							
								
								
									
										12
									
								
								docker/ha-addon-rootfs/etc/nginx/servers/direct.disabled
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								docker/ha-addon-rootfs/etc/nginx/servers/direct.disabled
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
server {
 | 
			
		||||
    listen %%port%% default_server;
 | 
			
		||||
 | 
			
		||||
    include /etc/nginx/includes/server_params.conf;
 | 
			
		||||
    include /etc/nginx/includes/proxy_params.conf;
 | 
			
		||||
    # Clear Hass.io Ingress header
 | 
			
		||||
    proxy_set_header X-HA-Ingress "";
 | 
			
		||||
 | 
			
		||||
    location / {
 | 
			
		||||
        proxy_pass http://esphome;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,16 +1,14 @@
 | 
			
		||||
server {
 | 
			
		||||
    listen 127.0.0.1:{{ .port }} default_server;
 | 
			
		||||
    listen {{ .interface }}:{{ .port }} default_server;
 | 
			
		||||
    listen %%interface%%:%%port%% default_server;
 | 
			
		||||
 | 
			
		||||
    include /etc/nginx/includes/server_params.conf;
 | 
			
		||||
    include /etc/nginx/includes/proxy_params.conf;
 | 
			
		||||
 | 
			
		||||
    # Set Home Assistant Ingress header
 | 
			
		||||
    proxy_set_header X-HA-Ingress "YES";
 | 
			
		||||
 | 
			
		||||
    location / {
 | 
			
		||||
        # Only allow from Hass.io supervisor
 | 
			
		||||
        allow   172.30.32.2;
 | 
			
		||||
        allow   127.0.0.1;
 | 
			
		||||
        deny    all;
 | 
			
		||||
 | 
			
		||||
        proxy_pass http://esphome;
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
#!/command/with-contenv bashio
 | 
			
		||||
# shellcheck shell=bash
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
# Home Assistant Add-on: ESPHome
 | 
			
		||||
# Sends discovery information to Home Assistant.
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
declare config
 | 
			
		||||
declare port
 | 
			
		||||
 | 
			
		||||
# We only disable it when disabled explicitly
 | 
			
		||||
if bashio::config.false 'home_assistant_dashboard_integration';
 | 
			
		||||
then
 | 
			
		||||
    bashio::log.info "Home Assistant discovery is disabled for this add-on."
 | 
			
		||||
    bashio::exit.ok
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
port=$(bashio::addon.ingress_port)
 | 
			
		||||
 | 
			
		||||
# Wait for NGINX to become available
 | 
			
		||||
bashio::net.wait_for "${port}" "127.0.0.1" 300
 | 
			
		||||
 | 
			
		||||
config=$(\
 | 
			
		||||
    bashio::var.json \
 | 
			
		||||
        host "127.0.0.1" \
 | 
			
		||||
        port "^${port}" \
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if bashio::discovery "esphome" "${config}" > /dev/null; then
 | 
			
		||||
    bashio::log.info "Successfully send discovery information to Home Assistant."
 | 
			
		||||
else
 | 
			
		||||
    bashio::log.error "Discovery message to Home Assistant failed!"
 | 
			
		||||
fi
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
oneshot
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
/etc/s6-overlay/s6-rc.d/discovery/run
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
#!/command/with-contenv bashio
 | 
			
		||||
# shellcheck shell=bash
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
# Home Assistant Community Add-on: ESPHome
 | 
			
		||||
# Take down the S6 supervision tree when ESPHome dashboard fails
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
declare exit_code
 | 
			
		||||
readonly exit_code_container=$(</run/s6-linux-init-container-results/exitcode)
 | 
			
		||||
readonly exit_code_service="${1}"
 | 
			
		||||
readonly exit_code_signal="${2}"
 | 
			
		||||
 | 
			
		||||
bashio::log.info \
 | 
			
		||||
  "Service ESPHome dashboard exited with code ${exit_code_service}" \
 | 
			
		||||
  "(by signal ${exit_code_signal})"
 | 
			
		||||
 | 
			
		||||
if [[ "${exit_code_service}" -eq 256 ]]; then
 | 
			
		||||
  if [[ "${exit_code_container}" -eq 0 ]]; then
 | 
			
		||||
    echo $((128 + $exit_code_signal)) > /run/s6-linux-init-container-results/exitcode
 | 
			
		||||
  fi
 | 
			
		||||
  [[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt
 | 
			
		||||
elif [[ "${exit_code_service}" -ne 0 ]]; then
 | 
			
		||||
  if [[ "${exit_code_container}" -eq 0 ]]; then
 | 
			
		||||
    echo "${exit_code_service}" > /run/s6-linux-init-container-results/exitcode
 | 
			
		||||
  fi
 | 
			
		||||
  exec /run/s6/basedir/bin/halt
 | 
			
		||||
fi
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
longrun
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
#!/command/with-contenv bashio
 | 
			
		||||
# shellcheck shell=bash
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
# Community Hass.io Add-ons: ESPHome
 | 
			
		||||
# Configures NGINX for use with ESPHome
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
mkdir -p /var/log/nginx
 | 
			
		||||
 | 
			
		||||
# Generate Ingress configuration
 | 
			
		||||
bashio::var.json \
 | 
			
		||||
    interface "$(bashio::addon.ip_address)" \
 | 
			
		||||
    port "^$(bashio::addon.ingress_port)" \
 | 
			
		||||
    | tempio \
 | 
			
		||||
        -template /etc/nginx/templates/ingress.gtpl \
 | 
			
		||||
        -out /etc/nginx/servers/ingress.conf
 | 
			
		||||
 | 
			
		||||
# Generate direct access configuration, if enabled.
 | 
			
		||||
if bashio::var.has_value "$(bashio::addon.port 6052)"; then
 | 
			
		||||
    bashio::config.require.ssl
 | 
			
		||||
    bashio::var.json \
 | 
			
		||||
        certfile "$(bashio::config 'certfile')" \
 | 
			
		||||
        keyfile "$(bashio::config 'keyfile')" \
 | 
			
		||||
        ssl "^$(bashio::config 'ssl')" \
 | 
			
		||||
        | tempio \
 | 
			
		||||
            -template /etc/nginx/templates/direct.gtpl \
 | 
			
		||||
            -out /etc/nginx/servers/direct.conf
 | 
			
		||||
fi
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
oneshot
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
/etc/s6-overlay/s6-rc.d/init-nginx/run
 | 
			
		||||
@@ -1,25 +0,0 @@
 | 
			
		||||
#!/command/with-contenv bashio
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
# Community Hass.io Add-ons: ESPHome
 | 
			
		||||
# Take down the S6 supervision tree when NGINX fails
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
declare exit_code
 | 
			
		||||
readonly exit_code_container=$(</run/s6-linux-init-container-results/exitcode)
 | 
			
		||||
readonly exit_code_service="${1}"
 | 
			
		||||
readonly exit_code_signal="${2}"
 | 
			
		||||
 | 
			
		||||
bashio::log.info \
 | 
			
		||||
  "Service NGINX exited with code ${exit_code_service}" \
 | 
			
		||||
  "(by signal ${exit_code_signal})"
 | 
			
		||||
 | 
			
		||||
if [[ "${exit_code_service}" -eq 256 ]]; then
 | 
			
		||||
  if [[ "${exit_code_container}" -eq 0 ]]; then
 | 
			
		||||
    echo $((128 + $exit_code_signal)) > /run/s6-linux-init-container-results/exitcode
 | 
			
		||||
  fi
 | 
			
		||||
  [[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt
 | 
			
		||||
elif [[ "${exit_code_service}" -ne 0 ]]; then
 | 
			
		||||
  if [[ "${exit_code_container}" -eq 0 ]]; then
 | 
			
		||||
    echo "${exit_code_service}" > /run/s6-linux-init-container-results/exitcode
 | 
			
		||||
  fi
 | 
			
		||||
  exec /run/s6/basedir/bin/halt
 | 
			
		||||
fi
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
longrun
 | 
			
		||||
							
								
								
									
										9
									
								
								docker/ha-addon-rootfs/etc/services.d/esphome/finish
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								docker/ha-addon-rootfs/etc/services.d/esphome/finish
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
#!/usr/bin/execlineb -S0
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
# Community Hass.io Add-ons: ESPHome
 | 
			
		||||
# Take down the S6 supervision tree when ESPHome fails
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
if -n { s6-test $# -ne 0 }
 | 
			
		||||
if -n { s6-test ${1} -eq 256 }
 | 
			
		||||
 | 
			
		||||
s6-svscanctl -t /var/run/s6/services
 | 
			
		||||
@@ -1,19 +1,10 @@
 | 
			
		||||
#!/command/with-contenv bashio
 | 
			
		||||
# shellcheck shell=bash
 | 
			
		||||
#!/usr/bin/with-contenv bashio
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
# Community Hass.io Add-ons: ESPHome
 | 
			
		||||
# Runs the ESPHome dashboard
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
readonly pio_cache_base=/data/cache/platformio
 | 
			
		||||
 | 
			
		||||
export ESPHOME_IS_HA_ADDON=true
 | 
			
		||||
export PLATFORMIO_GLOBALLIB_DIR=/piolibs
 | 
			
		||||
 | 
			
		||||
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
 | 
			
		||||
# setting `core_dir` would therefore prevent pio from accessing
 | 
			
		||||
export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
 | 
			
		||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
 | 
			
		||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
 | 
			
		||||
 | 
			
		||||
if bashio::config.true 'leave_front_door_open'; then
 | 
			
		||||
    export DISABLE_HA_AUTHENTICATION=true
 | 
			
		||||
@@ -31,15 +22,14 @@ if bashio::config.has_value 'relative_url'; then
 | 
			
		||||
    export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if bashio::config.has_value 'default_compile_process_limit'; then
 | 
			
		||||
    export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit')
 | 
			
		||||
else
 | 
			
		||||
    if grep -q 'Raspberry Pi 3' /proc/cpuinfo; then
 | 
			
		||||
        export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1;
 | 
			
		||||
    fi
 | 
			
		||||
fi
 | 
			
		||||
pio_cache_base=/data/cache/platformio
 | 
			
		||||
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
 | 
			
		||||
# setting `core_dir` would therefore prevent pio from accessing
 | 
			
		||||
export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
 | 
			
		||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
 | 
			
		||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
 | 
			
		||||
 | 
			
		||||
mkdir -p "${pio_cache_base}"
 | 
			
		||||
export PLATFORMIO_GLOBALLIB_DIR=/piolibs
 | 
			
		||||
 | 
			
		||||
bashio::log.info "Starting ESPHome dashboard..."
 | 
			
		||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon
 | 
			
		||||
							
								
								
									
										9
									
								
								docker/ha-addon-rootfs/etc/services.d/nginx/finish
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								docker/ha-addon-rootfs/etc/services.d/nginx/finish
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
#!/usr/bin/execlineb -S0
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
# Community Hass.io Add-ons: ESPHome
 | 
			
		||||
# Take down the S6 supervision tree when NGINX fails
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
if -n { s6-test $# -ne 0 }
 | 
			
		||||
if -n { s6-test ${1} -eq 256 }
 | 
			
		||||
 | 
			
		||||
s6-svscanctl -t /var/run/s6/services
 | 
			
		||||
@@ -1,11 +1,10 @@
 | 
			
		||||
#!/command/with-contenv bashio
 | 
			
		||||
# shellcheck shell=bash
 | 
			
		||||
#!/usr/bin/with-contenv bashio
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
# Community Hass.io Add-ons: ESPHome
 | 
			
		||||
# Runs the NGINX proxy
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
 | 
			
		||||
bashio::log.info "Waiting for ESPHome dashboard to come up..."
 | 
			
		||||
bashio::log.info "Waiting for dashboard to come up..."
 | 
			
		||||
 | 
			
		||||
while [[ ! -S /var/run/esphome.sock ]]; do
 | 
			
		||||
  sleep 0.5
 | 
			
		||||
							
								
								
									
										30
									
								
								docker/platformio_install_deps.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										30
									
								
								docker/platformio_install_deps.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
# This script is used in the docker containers to preinstall
 | 
			
		||||
# all platformio libraries in the global storage
 | 
			
		||||
 | 
			
		||||
import configparser
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
 | 
			
		||||
config.read(sys.argv[1])
 | 
			
		||||
 | 
			
		||||
libs = []
 | 
			
		||||
# Extract from every lib_deps key in all sections
 | 
			
		||||
for section in config.sections():
 | 
			
		||||
    conf = config[section]
 | 
			
		||||
    if "lib_deps" not in conf:
 | 
			
		||||
        continue
 | 
			
		||||
    for lib_dep in conf["lib_deps"].splitlines():
 | 
			
		||||
        if not lib_dep:
 | 
			
		||||
            # Empty line or comment
 | 
			
		||||
            continue
 | 
			
		||||
        if lib_dep.startswith("${"):
 | 
			
		||||
            # Extending from another section
 | 
			
		||||
            continue
 | 
			
		||||
        if "@" not in lib_dep:
 | 
			
		||||
            # No version pinned, this is an internal lib
 | 
			
		||||
            continue
 | 
			
		||||
        libs.append(lib_dep)
 | 
			
		||||
 | 
			
		||||
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])
 | 
			
		||||
@@ -18,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,
 | 
			
		||||
@@ -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):
 | 
			
		||||
@@ -159,8 +152,6 @@ def run_miniterm(config, port):
 | 
			
		||||
        _LOGGER.error("Could not connect to serial port %s", port)
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    return 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def wrap_to_code(name, comp):
 | 
			
		||||
    coro = coroutine(comp.to_code)
 | 
			
		||||
@@ -263,7 +254,8 @@ def upload_using_esptool(config, port):
 | 
			
		||||
        if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
 | 
			
		||||
            import esptool
 | 
			
		||||
 | 
			
		||||
            return run_external_command(esptool.main, *cmd)  # pylint: disable=no-member
 | 
			
		||||
            # pylint: disable=protected-access
 | 
			
		||||
            return run_external_command(esptool._main, *cmd)
 | 
			
		||||
 | 
			
		||||
        return run_external_process(*cmd)
 | 
			
		||||
 | 
			
		||||
@@ -295,30 +287,17 @@ 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 (
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -328,13 +307,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)
 | 
			
		||||
@@ -366,7 +338,7 @@ def command_config(args, config):
 | 
			
		||||
    _LOGGER.info("Configuration is valid!")
 | 
			
		||||
    if not CORE.verbose:
 | 
			
		||||
        config = strip_default_ids(config)
 | 
			
		||||
    safe_print(yaml_util.dump(config, args.show_secrets))
 | 
			
		||||
    safe_print(yaml_util.dump(config))
 | 
			
		||||
    return 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -399,7 +371,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:
 | 
			
		||||
@@ -408,15 +379,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,
 | 
			
		||||
@@ -424,7 +386,6 @@ def command_logs(args, config):
 | 
			
		||||
        show_ota=False,
 | 
			
		||||
        show_mqtt=True,
 | 
			
		||||
        show_api=True,
 | 
			
		||||
        purpose="logging",
 | 
			
		||||
    )
 | 
			
		||||
    return show_logs(config, args, port)
 | 
			
		||||
 | 
			
		||||
@@ -443,7 +404,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:
 | 
			
		||||
@@ -457,7 +417,6 @@ def command_run(args, config):
 | 
			
		||||
        show_ota=False,
 | 
			
		||||
        show_mqtt=True,
 | 
			
		||||
        show_api=True,
 | 
			
		||||
        purpose="logging",
 | 
			
		||||
    )
 | 
			
		||||
    return show_logs(config, args, port)
 | 
			
		||||
 | 
			
		||||
@@ -661,7 +620,6 @@ POST_CONFIG_ACTIONS = {
 | 
			
		||||
    "clean": command_clean,
 | 
			
		||||
    "idedata": command_idedata,
 | 
			
		||||
    "rename": command_rename,
 | 
			
		||||
    "discover": command_discover,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -706,9 +664,6 @@ def parse_args(argv):
 | 
			
		||||
    parser_config.add_argument(
 | 
			
		||||
        "configuration", help="Your YAML configuration file(s).", nargs="+"
 | 
			
		||||
    )
 | 
			
		||||
    parser_config.add_argument(
 | 
			
		||||
        "--show-secrets", help="Show secrets in output.", action="store_true"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser_compile = subparsers.add_parser(
 | 
			
		||||
        "compile", help="Read the configuration and compile a program."
 | 
			
		||||
@@ -732,10 +687,6 @@ def parse_args(argv):
 | 
			
		||||
        "--device",
 | 
			
		||||
        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
			
		||||
    )
 | 
			
		||||
    parser_upload.add_argument(
 | 
			
		||||
        "--file",
 | 
			
		||||
        help="Manually specify the binary file to upload.",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser_logs = subparsers.add_parser(
 | 
			
		||||
        "logs",
 | 
			
		||||
@@ -750,15 +701,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.",
 | 
			
		||||
@@ -980,8 +922,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)
 | 
			
		||||
 
 | 
			
		||||
@@ -254,11 +254,7 @@ async def repeat_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg)
 | 
			
		||||
    count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
 | 
			
		||||
    cg.add(var.set_count(count_template))
 | 
			
		||||
    actions = await build_action_list(
 | 
			
		||||
        config[CONF_THEN],
 | 
			
		||||
        cg.TemplateArguments(cg.uint32, *template_arg.args),
 | 
			
		||||
        [(cg.uint32, "iteration"), *args],
 | 
			
		||||
    )
 | 
			
		||||
    actions = await build_action_list(config[CONF_THEN], template_arg, args)
 | 
			
		||||
    cg.add(var.add_then(actions))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,6 @@ from esphome.cpp_helpers import (  # noqa
 | 
			
		||||
    build_registry_list,
 | 
			
		||||
    extract_registry_entry_config,
 | 
			
		||||
    register_parented,
 | 
			
		||||
    past_safe_mode,
 | 
			
		||||
)
 | 
			
		||||
from esphome.cpp_types import (  # noqa
 | 
			
		||||
    global_ns,
 | 
			
		||||
@@ -64,7 +63,6 @@ from esphome.cpp_types import (  # noqa
 | 
			
		||||
    uint16,
 | 
			
		||||
    uint32,
 | 
			
		||||
    uint64,
 | 
			
		||||
    int16,
 | 
			
		||||
    int32,
 | 
			
		||||
    int64,
 | 
			
		||||
    size_t,
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,6 @@ void A4988::loop() {
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  this->dir_pin_->digital_write(dir == 1);
 | 
			
		||||
  delayMicroseconds(50);
 | 
			
		||||
  this->step_pin_->digital_write(true);
 | 
			
		||||
  delayMicroseconds(5);
 | 
			
		||||
  this->step_pin_->digital_write(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
CODEOWNERS = ["@DAVe3283"]
 | 
			
		||||
@@ -1,182 +0,0 @@
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "absolute_humidity.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace absolute_humidity {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "absolute_humidity.sensor";
 | 
			
		||||
 | 
			
		||||
void AbsoluteHumidityComponent::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up absolute humidity '%s'...", this->get_name().c_str());
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "  Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str());
 | 
			
		||||
  this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); });
 | 
			
		||||
  if (this->temperature_sensor_->has_state()) {
 | 
			
		||||
    this->temperature_callback_(this->temperature_sensor_->get_state());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "  Added callback for relative humidity '%s'", this->humidity_sensor_->get_name().c_str());
 | 
			
		||||
  this->humidity_sensor_->add_on_state_callback([this](float state) { this->humidity_callback_(state); });
 | 
			
		||||
  if (this->humidity_sensor_->has_state()) {
 | 
			
		||||
    this->humidity_callback_(this->humidity_sensor_->get_state());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AbsoluteHumidityComponent::dump_config() {
 | 
			
		||||
  LOG_SENSOR("", "Absolute Humidity", this);
 | 
			
		||||
 | 
			
		||||
  switch (this->equation_) {
 | 
			
		||||
    case BUCK:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Buck");
 | 
			
		||||
      break;
 | 
			
		||||
    case TETENS:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Tetens");
 | 
			
		||||
      break;
 | 
			
		||||
    case WOBUS:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Wobus");
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Sources");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Temperature: '%s'", this->temperature_sensor_->get_name().c_str());
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Relative Humidity: '%s'", this->humidity_sensor_->get_name().c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float AbsoluteHumidityComponent::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
void AbsoluteHumidityComponent::loop() {
 | 
			
		||||
  if (!this->next_update_) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->next_update_ = false;
 | 
			
		||||
 | 
			
		||||
  // Ensure we have source data
 | 
			
		||||
  const bool no_temperature = std::isnan(this->temperature_);
 | 
			
		||||
  const bool no_humidity = std::isnan(this->humidity_);
 | 
			
		||||
  if (no_temperature || no_humidity) {
 | 
			
		||||
    if (no_temperature) {
 | 
			
		||||
      ESP_LOGW(TAG, "No valid state from temperature sensor!");
 | 
			
		||||
    }
 | 
			
		||||
    if (no_humidity) {
 | 
			
		||||
      ESP_LOGW(TAG, "No valid state from temperature sensor!");
 | 
			
		||||
    }
 | 
			
		||||
    ESP_LOGW(TAG, "Unable to calculate absolute humidity.");
 | 
			
		||||
    this->publish_state(NAN);
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Convert to desired units
 | 
			
		||||
  const float temperature_c = this->temperature_;
 | 
			
		||||
  const float temperature_k = temperature_c + 273.15;
 | 
			
		||||
  const float hr = this->humidity_ / 100;
 | 
			
		||||
 | 
			
		||||
  // Calculate saturation vapor pressure
 | 
			
		||||
  float es;
 | 
			
		||||
  switch (this->equation_) {
 | 
			
		||||
    case BUCK:
 | 
			
		||||
      es = es_buck(temperature_c);
 | 
			
		||||
      break;
 | 
			
		||||
    case TETENS:
 | 
			
		||||
      es = es_tetens(temperature_c);
 | 
			
		||||
      break;
 | 
			
		||||
    case WOBUS:
 | 
			
		||||
      es = es_wobus(temperature_c);
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
 | 
			
		||||
      this->publish_state(NAN);
 | 
			
		||||
      this->status_set_error();
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
 | 
			
		||||
 | 
			
		||||
  // Calculate absolute humidity
 | 
			
		||||
  const float absolute_humidity = vapor_density(es, hr, temperature_k);
 | 
			
		||||
 | 
			
		||||
  // Publish absolute humidity
 | 
			
		||||
  ESP_LOGD(TAG, "Publishing absolute humidity %f g/m³", absolute_humidity);
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
  this->publish_state(absolute_humidity);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Buck equation (https://en.wikipedia.org/wiki/Arden_Buck_equation)
 | 
			
		||||
// More accurate than Tetens in normal meteorologic conditions
 | 
			
		||||
float AbsoluteHumidityComponent::es_buck(float temperature_c) {
 | 
			
		||||
  float a, b, c, d;
 | 
			
		||||
  if (temperature_c >= 0) {
 | 
			
		||||
    a = 0.61121;
 | 
			
		||||
    b = 18.678;
 | 
			
		||||
    c = 234.5;
 | 
			
		||||
    d = 257.14;
 | 
			
		||||
  } else {
 | 
			
		||||
    a = 0.61115;
 | 
			
		||||
    b = 18.678;
 | 
			
		||||
    c = 233.7;
 | 
			
		||||
    d = 279.82;
 | 
			
		||||
  }
 | 
			
		||||
  return a * expf((b - (temperature_c / c)) * (temperature_c / (d + temperature_c)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Tetens equation (https://en.wikipedia.org/wiki/Tetens_equation)
 | 
			
		||||
float AbsoluteHumidityComponent::es_tetens(float temperature_c) {
 | 
			
		||||
  float a, b;
 | 
			
		||||
  if (temperature_c >= 0) {
 | 
			
		||||
    a = 17.27;
 | 
			
		||||
    b = 237.3;
 | 
			
		||||
  } else {
 | 
			
		||||
    a = 21.875;
 | 
			
		||||
    b = 265.5;
 | 
			
		||||
  }
 | 
			
		||||
  return 0.61078 * expf((a * temperature_c) / (temperature_c + b));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Wobus equation
 | 
			
		||||
// https://wahiduddin.net/calc/density_altitude.htm
 | 
			
		||||
// https://wahiduddin.net/calc/density_algorithms.htm
 | 
			
		||||
// Calculate the saturation vapor pressure (kPa)
 | 
			
		||||
float AbsoluteHumidityComponent::es_wobus(float t) {
 | 
			
		||||
  // THIS FUNCTION RETURNS THE SATURATION VAPOR PRESSURE ESW (MILLIBARS)
 | 
			
		||||
  // OVER LIQUID WATER GIVEN THE TEMPERATURE T (CELSIUS). THE POLYNOMIAL
 | 
			
		||||
  // APPROXIMATION BELOW IS DUE TO HERMAN WOBUS, A MATHEMATICIAN WHO
 | 
			
		||||
  // WORKED AT THE NAVY WEATHER RESEARCH FACILITY, NORFOLK, VIRGINIA,
 | 
			
		||||
  // BUT WHO IS NOW RETIRED. THE COEFFICIENTS OF THE POLYNOMIAL WERE
 | 
			
		||||
  // CHOSEN TO FIT THE VALUES IN TABLE 94 ON PP. 351-353 OF THE SMITH-
 | 
			
		||||
  // SONIAN METEOROLOGICAL TABLES BY ROLAND LIST (6TH EDITION). THE
 | 
			
		||||
  // APPROXIMATION IS VALID FOR -50 < T < 100C.
 | 
			
		||||
  //
 | 
			
		||||
  //     Baker, Schlatter  17-MAY-1982     Original version.
 | 
			
		||||
 | 
			
		||||
  const float c0 = +0.99999683e00;
 | 
			
		||||
  const float c1 = -0.90826951e-02;
 | 
			
		||||
  const float c2 = +0.78736169e-04;
 | 
			
		||||
  const float c3 = -0.61117958e-06;
 | 
			
		||||
  const float c4 = +0.43884187e-08;
 | 
			
		||||
  const float c5 = -0.29883885e-10;
 | 
			
		||||
  const float c6 = +0.21874425e-12;
 | 
			
		||||
  const float c7 = -0.17892321e-14;
 | 
			
		||||
  const float c8 = +0.11112018e-16;
 | 
			
		||||
  const float c9 = -0.30994571e-19;
 | 
			
		||||
  const float p = c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * (c6 + t * (c7 + t * (c8 + t * (c9)))))))));
 | 
			
		||||
  return 0.61078 / pow(p, 8);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// From https://www.environmentalbiophysics.org/chalk-talk-how-to-calculate-absolute-humidity/
 | 
			
		||||
// H/T to https://esphome.io/cookbook/bme280_environment.html
 | 
			
		||||
// H/T to https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
 | 
			
		||||
float AbsoluteHumidityComponent::vapor_density(float es, float hr, float ta) {
 | 
			
		||||
  // es = saturated vapor pressure (kPa)
 | 
			
		||||
  // hr = relative humidity [0-1]
 | 
			
		||||
  // ta = absolute temperature (K)
 | 
			
		||||
 | 
			
		||||
  const float ea = hr * es * 1000;   // vapor pressure of the air (Pa)
 | 
			
		||||
  const float mw = 18.01528;         // molar mass of water (g⋅mol⁻¹)
 | 
			
		||||
  const float r = 8.31446261815324;  // molar gas constant (J⋅K⁻¹)
 | 
			
		||||
  return (ea * mw) / (r * ta);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace absolute_humidity
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,76 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace absolute_humidity {
 | 
			
		||||
 | 
			
		||||
/// Enum listing all implemented saturation vapor pressure equations.
 | 
			
		||||
enum SaturationVaporPressureEquation {
 | 
			
		||||
  BUCK,
 | 
			
		||||
  TETENS,
 | 
			
		||||
  WOBUS,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// This class implements calculation of absolute humidity from temperature and relative humidity.
 | 
			
		||||
class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  AbsoluteHumidityComponent() = default;
 | 
			
		||||
 | 
			
		||||
  void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
 | 
			
		||||
  void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
 | 
			
		||||
  void set_equation(SaturationVaporPressureEquation equation) { this->equation_ = equation; }
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void temperature_callback_(float state) {
 | 
			
		||||
    this->next_update_ = true;
 | 
			
		||||
    this->temperature_ = state;
 | 
			
		||||
  }
 | 
			
		||||
  void humidity_callback_(float state) {
 | 
			
		||||
    this->next_update_ = true;
 | 
			
		||||
    this->humidity_ = state;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Buck equation for saturation vapor pressure in kPa.
 | 
			
		||||
   *
 | 
			
		||||
   * @param temperature_c Air temperature in °C.
 | 
			
		||||
   */
 | 
			
		||||
  static float es_buck(float temperature_c);
 | 
			
		||||
  /** Tetens equation for saturation vapor pressure in kPa.
 | 
			
		||||
   *
 | 
			
		||||
   * @param temperature_c Air temperature in °C.
 | 
			
		||||
   */
 | 
			
		||||
  static float es_tetens(float temperature_c);
 | 
			
		||||
  /** Wobus equation for saturation vapor pressure in kPa.
 | 
			
		||||
   *
 | 
			
		||||
   * @param temperature_c Air temperature in °C.
 | 
			
		||||
   */
 | 
			
		||||
  static float es_wobus(float temperature_c);
 | 
			
		||||
 | 
			
		||||
  /** Calculate vapor density (absolute humidity) in g/m³.
 | 
			
		||||
   *
 | 
			
		||||
   * @param es Saturation vapor pressure in kPa.
 | 
			
		||||
   * @param hr Relative humidity 0 to 1.
 | 
			
		||||
   * @param ta Absolute temperature in K.
 | 
			
		||||
   * @param heater_duration The duration in ms that the heater should turn on for when measuring.
 | 
			
		||||
   */
 | 
			
		||||
  static float vapor_density(float es, float hr, float ta);
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *temperature_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *humidity_sensor_{nullptr};
 | 
			
		||||
 | 
			
		||||
  bool next_update_{false};
 | 
			
		||||
 | 
			
		||||
  float temperature_{NAN};
 | 
			
		||||
  float humidity_{NAN};
 | 
			
		||||
  SaturationVaporPressureEquation equation_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace absolute_humidity
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    CONF_EQUATION,
 | 
			
		||||
    ICON_WATER,
 | 
			
		||||
    UNIT_GRAMS_PER_CUBIC_METER,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
absolute_humidity_ns = cg.esphome_ns.namespace("absolute_humidity")
 | 
			
		||||
AbsoluteHumidityComponent = absolute_humidity_ns.class_(
 | 
			
		||||
    "AbsoluteHumidityComponent", sensor.Sensor, cg.Component
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
SaturationVaporPressureEquation = absolute_humidity_ns.enum(
 | 
			
		||||
    "SaturationVaporPressureEquation"
 | 
			
		||||
)
 | 
			
		||||
EQUATION = {
 | 
			
		||||
    "BUCK": SaturationVaporPressureEquation.BUCK,
 | 
			
		||||
    "TETENS": SaturationVaporPressureEquation.TETENS,
 | 
			
		||||
    "WOBUS": SaturationVaporPressureEquation.WOBUS,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    sensor.sensor_schema(
 | 
			
		||||
        unit_of_measurement=UNIT_GRAMS_PER_CUBIC_METER,
 | 
			
		||||
        icon=ICON_WATER,
 | 
			
		||||
        accuracy_decimals=2,
 | 
			
		||||
        state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    )
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(AbsoluteHumidityComponent),
 | 
			
		||||
            cv.Required(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
 | 
			
		||||
            cv.Required(CONF_HUMIDITY): cv.use_id(sensor.Sensor),
 | 
			
		||||
            cv.Optional(CONF_EQUATION, default="WOBUS"): cv.enum(EQUATION, upper=True),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await sensor.new_sensor(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    temperature_sensor = await cg.get_variable(config[CONF_TEMPERATURE])
 | 
			
		||||
    cg.add(var.set_temperature_sensor(temperature_sensor))
 | 
			
		||||
 | 
			
		||||
    humidity_sensor = await cg.get_variable(config[CONF_HUMIDITY])
 | 
			
		||||
    cg.add(var.set_humidity_sensor(humidity_sensor))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_equation(config[CONF_EQUATION]))
 | 
			
		||||
@@ -1,118 +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")
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,6 @@ ADC_MODE(ADC_VCC)
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
#include <hardware/adc.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace adc {
 | 
			
		||||
 | 
			
		||||
@@ -36,13 +32,9 @@ static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1;    // 4095 (12 b
 | 
			
		||||
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;  // 2048 (12 bit) or 4096 (13 bit)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
extern "C"
 | 
			
		||||
#endif
 | 
			
		||||
    void
 | 
			
		||||
    ADCSensor::setup() {
 | 
			
		||||
void ADCSensor::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
 | 
			
		||||
#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040)
 | 
			
		||||
#ifndef USE_ADC_SENSOR_VCC
 | 
			
		||||
  pin_->setup();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -71,16 +63,6 @@ extern "C"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  static bool initialized = false;
 | 
			
		||||
  if (!initialized) {
 | 
			
		||||
    adc_init();
 | 
			
		||||
    initialized = true;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADCSensor::dump_config() {
 | 
			
		||||
@@ -116,13 +98,6 @@ void ADCSensor::dump_config() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  if (this->is_temperature_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Pin: Temperature");
 | 
			
		||||
  } else {
 | 
			
		||||
    LOG_PIN("  Pin: ", pin_);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -200,29 +175,6 @@ float ADCSensor::sample() {
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
float ADCSensor::sample() {
 | 
			
		||||
  if (this->is_temperature_) {
 | 
			
		||||
    adc_set_temp_sensor_enabled(true);
 | 
			
		||||
    delay(1);
 | 
			
		||||
    adc_select_input(4);
 | 
			
		||||
  } else {
 | 
			
		||||
    uint8_t pin = this->pin_->get_pin();
 | 
			
		||||
    adc_gpio_init(pin);
 | 
			
		||||
    adc_select_input(pin - 26);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int raw = adc_read();
 | 
			
		||||
  if (this->is_temperature_) {
 | 
			
		||||
    adc_set_temp_sensor_enabled(false);
 | 
			
		||||
  }
 | 
			
		||||
  if (output_raw_) {
 | 
			
		||||
    return raw;
 | 
			
		||||
  }
 | 
			
		||||
  return raw * 3.3f / 4096.0f;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -38,18 +38,10 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
  std::string unique_id() override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  void set_is_temperature() { is_temperature_ = true; }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  InternalGPIOPin *pin_;
 | 
			
		||||
  bool output_raw_{false};
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  bool is_temperature_{false};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  adc_atten_t attenuation_{ADC_ATTEN_DB_0};
 | 
			
		||||
  adc1_channel_t channel_{};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,124 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
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,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
 | 
			
		||||
from . import (
 | 
			
		||||
    ATTENUATION_MODES,
 | 
			
		||||
    ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
 | 
			
		||||
    validate_adc_pin,
 | 
			
		||||
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 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)
 | 
			
		||||
 | 
			
		||||
    raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_config(config):
 | 
			
		||||
    if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
 | 
			
		||||
@@ -63,8 +160,6 @@ async def to_code(config):
 | 
			
		||||
 | 
			
		||||
    if config[CONF_PIN] == "VCC":
 | 
			
		||||
        cg.add_define("USE_ADC_SENSOR_VCC")
 | 
			
		||||
    elif config[CONF_PIN] == "TEMPERATURE":
 | 
			
		||||
        cg.add(var.set_is_temperature())
 | 
			
		||||
    else:
 | 
			
		||||
        pin = await cg.gpio_pin_expression(config[CONF_PIN])
 | 
			
		||||
        cg.add(var.set_pin(pin))
 | 
			
		||||
 
 | 
			
		||||
@@ -16,16 +16,13 @@ ADC128S102Sensor = adc128s102_ns.class_(
 | 
			
		||||
)
 | 
			
		||||
CONF_ADC128S102_ID = "adc128s102_id"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    sensor.sensor_schema(ADC128S102Sensor)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102),
 | 
			
		||||
            cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
)
 | 
			
		||||
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(ADC128S102Sensor),
 | 
			
		||||
        cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102),
 | 
			
		||||
        cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.polling_component_schema("60s"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,6 @@
 | 
			
		||||
#include "esphome/components/display/display_buffer.h"
 | 
			
		||||
#include "esphome/components/light/addressable_light.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace addressable_light {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,6 @@
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ade7953 {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ static const char *const TAG = "ads1115";
 | 
			
		||||
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
 | 
			
		||||
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
 | 
			
		||||
 | 
			
		||||
static const uint8_t ADS1115_DATA_RATE_860_SPS = 0b111;  // 3300_SPS for ADS1015
 | 
			
		||||
static const uint8_t ADS1115_DATA_RATE_860_SPS = 0b111;
 | 
			
		||||
 | 
			
		||||
void ADS1115Component::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
 | 
			
		||||
@@ -18,9 +18,6 @@ void ADS1115Component::setup() {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Configuring ADS1115...");
 | 
			
		||||
 | 
			
		||||
  uint16_t config = 0;
 | 
			
		||||
  // Clear single-shot bit
 | 
			
		||||
  //        0b0xxxxxxxxxxxxxxx
 | 
			
		||||
@@ -80,7 +77,6 @@ void ADS1115Component::dump_config() {
 | 
			
		||||
    LOG_SENSOR("  ", "Sensor", sensor);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Multiplexer: %u", sensor->get_multiplexer());
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Gain: %u", sensor->get_gain());
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Resolution: %u", sensor->get_resolution());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
 | 
			
		||||
@@ -131,45 +127,27 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return NAN;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (sensor->get_resolution() == ADS1015_12_BITS) {
 | 
			
		||||
    bool negative = (raw_conversion >> 15) == 1;
 | 
			
		||||
 | 
			
		||||
    // shift raw_conversion as it's only 12-bits, left justified
 | 
			
		||||
    raw_conversion = raw_conversion >> (16 - ADS1015_12_BITS);
 | 
			
		||||
 | 
			
		||||
    // check if number was negative in order to keep the sign
 | 
			
		||||
    if (negative) {
 | 
			
		||||
      // the number was negative
 | 
			
		||||
      // 1) set the negative bit back
 | 
			
		||||
      raw_conversion |= 0x8000;
 | 
			
		||||
      // 2) reset the former (shifted) negative bit
 | 
			
		||||
      raw_conversion &= 0xF7FF;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto signed_conversion = static_cast<int16_t>(raw_conversion);
 | 
			
		||||
 | 
			
		||||
  float millivolts;
 | 
			
		||||
  float divider = (sensor->get_resolution() == ADS1115_16_BITS) ? 32768.0f : 2048.0f;
 | 
			
		||||
  switch (sensor->get_gain()) {
 | 
			
		||||
    case ADS1115_GAIN_6P144:
 | 
			
		||||
      millivolts = (signed_conversion * 6144) / divider;
 | 
			
		||||
      millivolts = signed_conversion * 0.187500f;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADS1115_GAIN_4P096:
 | 
			
		||||
      millivolts = (signed_conversion * 4096) / divider;
 | 
			
		||||
      millivolts = signed_conversion * 0.125000f;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADS1115_GAIN_2P048:
 | 
			
		||||
      millivolts = (signed_conversion * 2048) / divider;
 | 
			
		||||
      millivolts = signed_conversion * 0.062500f;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADS1115_GAIN_1P024:
 | 
			
		||||
      millivolts = (signed_conversion * 1024) / divider;
 | 
			
		||||
      millivolts = signed_conversion * 0.031250f;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADS1115_GAIN_0P512:
 | 
			
		||||
      millivolts = (signed_conversion * 512) / divider;
 | 
			
		||||
      millivolts = signed_conversion * 0.015625f;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADS1115_GAIN_0P256:
 | 
			
		||||
      millivolts = (signed_conversion * 256) / divider;
 | 
			
		||||
      millivolts = signed_conversion * 0.007813f;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      millivolts = NAN;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,6 @@
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ads1115 {
 | 
			
		||||
 | 
			
		||||
@@ -30,11 +28,6 @@ enum ADS1115Gain {
 | 
			
		||||
  ADS1115_GAIN_0P256 = 0b101,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum ADS1115Resolution {
 | 
			
		||||
  ADS1115_16_BITS = 16,
 | 
			
		||||
  ADS1015_12_BITS = 12,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ADS1115Sensor;
 | 
			
		||||
 | 
			
		||||
class ADS1115Component : public Component, public i2c::I2CDevice {
 | 
			
		||||
@@ -63,17 +56,15 @@ class ADS1115Sensor : public sensor::Sensor, public PollingComponent, public vol
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void set_multiplexer(ADS1115Multiplexer multiplexer) { multiplexer_ = multiplexer; }
 | 
			
		||||
  void set_gain(ADS1115Gain gain) { gain_ = gain; }
 | 
			
		||||
  void set_resolution(ADS1115Resolution resolution) { resolution_ = resolution; }
 | 
			
		||||
 | 
			
		||||
  float sample() override;
 | 
			
		||||
  uint8_t get_multiplexer() const { return multiplexer_; }
 | 
			
		||||
  uint8_t get_gain() const { return gain_; }
 | 
			
		||||
  uint8_t get_resolution() const { return resolution_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  ADS1115Component *parent_;
 | 
			
		||||
  ADS1115Multiplexer multiplexer_;
 | 
			
		||||
  ADS1115Gain gain_;
 | 
			
		||||
  ADS1115Resolution resolution_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ads1115
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ from esphome.components import sensor, voltage_sampler
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_GAIN,
 | 
			
		||||
    CONF_MULTIPLEXER,
 | 
			
		||||
    CONF_RESOLUTION,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
@@ -36,12 +35,6 @@ GAIN = {
 | 
			
		||||
    "0.256": ADS1115Gain.ADS1115_GAIN_0P256,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADS1115Resolution = ads1115_ns.enum("ADS1115Resolution")
 | 
			
		||||
RESOLUTION = {
 | 
			
		||||
    "16_BITS": ADS1115Resolution.ADS1115_16_BITS,
 | 
			
		||||
    "12_BITS": ADS1115Resolution.ADS1015_12_BITS,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_gain(value):
 | 
			
		||||
    if isinstance(value, float):
 | 
			
		||||
@@ -70,9 +63,6 @@ CONFIG_SCHEMA = (
 | 
			
		||||
            cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
 | 
			
		||||
            cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"),
 | 
			
		||||
            cv.Required(CONF_GAIN): validate_gain,
 | 
			
		||||
            cv.Optional(CONF_RESOLUTION, default="16_BITS"): cv.enum(
 | 
			
		||||
                RESOLUTION, upper=True, space="_"
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
@@ -87,6 +77,5 @@ async def to_code(config):
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
 | 
			
		||||
    cg.add(var.set_gain(config[CONF_GAIN]))
 | 
			
		||||
    cg.add(var.set_resolution(config[CONF_RESOLUTION]))
 | 
			
		||||
 | 
			
		||||
    cg.add(paren.register_sensor(var))
 | 
			
		||||
 
 | 
			
		||||
@@ -122,9 +122,8 @@ void AHT10Component::update() {
 | 
			
		||||
    this->temperature_sensor_->publish_state(temperature);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->humidity_sensor_ != nullptr) {
 | 
			
		||||
    if (std::isnan(humidity)) {
 | 
			
		||||
    if (std::isnan(humidity))
 | 
			
		||||
      ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
 | 
			
		||||
    }
 | 
			
		||||
    this->humidity_sensor_->publish_state(humidity);
 | 
			
		||||
  }
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = ["@buxtronix"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
#include "am43_sensor.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "am43.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
@@ -104,9 +104,8 @@ void Am43::update() {
 | 
			
		||||
      auto status =
 | 
			
		||||
          esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                   packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
      if (status) {
 | 
			
		||||
      if (status)
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this->current_sensor_++;
 | 
			
		||||
  }
 | 
			
		||||
@@ -5,7 +5,7 @@ from esphome.const import CONF_ID, CONF_PIN
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@buxtronix"]
 | 
			
		||||
DEPENDENCIES = ["ble_client"]
 | 
			
		||||
AUTO_LOAD = ["am43"]
 | 
			
		||||
AUTO_LOAD = ["am43", "sensor"]
 | 
			
		||||
 | 
			
		||||
CONF_INVERT_POSITION = "invert_position"
 | 
			
		||||
 | 
			
		||||
@@ -27,10 +27,10 @@ CONFIG_SCHEMA = (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    cg.add(var.set_pin(config[CONF_PIN]))
 | 
			
		||||
    cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await cover.register_cover(var, config)
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield cover.register_cover(var, config)
 | 
			
		||||
    yield ble_client.register_ble_node(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,6 @@ void Am43Component::loop() {
 | 
			
		||||
 | 
			
		||||
CoverTraits Am43Component::get_traits() {
 | 
			
		||||
  auto traits = CoverTraits();
 | 
			
		||||
  traits.set_supports_stop(true);
 | 
			
		||||
  traits.set_supports_position(true);
 | 
			
		||||
  traits.set_supports_tilt(false);
 | 
			
		||||
  traits.set_is_assumed_state(false);
 | 
			
		||||
@@ -57,22 +56,20 @@ void Am43Component::control(const CoverCall &call) {
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status) {
 | 
			
		||||
    if (status)
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (call.get_position().has_value()) {
 | 
			
		||||
    auto pos = *call.get_position();
 | 
			
		||||
 | 
			
		||||
    if (this->invert_position_)
 | 
			
		||||
      pos = 1 - pos;
 | 
			
		||||
    auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t) (pos * 100));
 | 
			
		||||
    auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status) {
 | 
			
		||||
    if (status)
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -129,21 +126,18 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
          auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
 | 
			
		||||
                                                 this->char_handle_, packet->length, packet->data,
 | 
			
		||||
                                                 ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
          if (status) {
 | 
			
		||||
          if (status)
 | 
			
		||||
            ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          ESP_LOGW(TAG, "[%s] AM43 pin rejected!", this->get_name().c_str());
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_) {
 | 
			
		||||
      if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_)
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] Got nack after sending set_position. Bad pin?", this->get_name().c_str());
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_) {
 | 
			
		||||
      if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_)
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] Got nack after sending set_state. Bad pin?", this->get_name().c_str());
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@ from esphome.const import (
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["am43"]
 | 
			
		||||
CODEOWNERS = ["@buxtronix"]
 | 
			
		||||
 | 
			
		||||
am43_ns = cg.esphome_ns.namespace("am43")
 | 
			
		||||
@@ -39,15 +38,15 @@ CONFIG_SCHEMA = (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
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)
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield ble_client.register_ble_node(var, config)
 | 
			
		||||
 | 
			
		||||
    if CONF_BATTERY_LEVEL in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
 | 
			
		||||
        sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
 | 
			
		||||
        cg.add(var.set_battery(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_ILLUMINANCE in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_ILLUMINANCE])
 | 
			
		||||
        sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE])
 | 
			
		||||
        cg.add(var.set_illuminance(sens))
 | 
			
		||||
@@ -15,24 +15,18 @@ AnalogThresholdBinarySensor = analog_threshold_ns.class_(
 | 
			
		||||
CONF_UPPER = "upper"
 | 
			
		||||
CONF_LOWER = "lower"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    binary_sensor.binary_sensor_schema(AnalogThresholdBinarySensor)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
 | 
			
		||||
            cv.Required(CONF_THRESHOLD): cv.Any(
 | 
			
		||||
                cv.float_,
 | 
			
		||||
                cv.Schema(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.Required(CONF_UPPER): cv.float_,
 | 
			
		||||
                        cv.Required(CONF_LOWER): cv.float_,
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(AnalogThresholdBinarySensor),
 | 
			
		||||
        cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
 | 
			
		||||
        cv.Required(CONF_THRESHOLD): cv.Any(
 | 
			
		||||
            cv.float_,
 | 
			
		||||
            cv.Schema(
 | 
			
		||||
                {cv.Required(CONF_UPPER): cv.float_, cv.Required(CONF_LOWER): cv.float_}
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -3,17 +3,9 @@ import logging
 | 
			
		||||
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__)
 | 
			
		||||
@@ -21,55 +13,18 @@ _LOGGER = logging.getLogger(__name__)
 | 
			
		||||
DEPENDENCIES = ["display"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONF_LOOP = "loop"
 | 
			
		||||
CONF_START_FRAME = "start_frame"
 | 
			
		||||
CONF_END_FRAME = "end_frame"
 | 
			
		||||
 | 
			
		||||
Animation_ = display.display_ns.class_("Animation", espImage.Image_)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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_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)
 | 
			
		||||
@@ -95,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())
 | 
			
		||||
@@ -115,22 +67,18 @@ 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")
 | 
			
		||||
            if CONF_RESIZE in config:
 | 
			
		||||
                image.thumbnail(config[CONF_RESIZE])
 | 
			
		||||
            frame = image.convert("RGB")
 | 
			
		||||
            if CONF_RESIZE in config:
 | 
			
		||||
                frame = frame.resize([width, height])
 | 
			
		||||
            pixels = list(frame.getdata())
 | 
			
		||||
@@ -145,44 +93,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())
 | 
			
		||||
@@ -190,53 +107,34 @@ 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"]:
 | 
			
		||||
    elif config[CONF_TYPE] == "BINARY":
 | 
			
		||||
        width8 = ((width + 7) // 8) * 8
 | 
			
		||||
        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,
 | 
			
		||||
@@ -244,9 +142,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))
 | 
			
		||||
 
 | 
			
		||||
@@ -37,18 +37,16 @@ void Anova::control(const ClimateCall &call) {
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status) {
 | 
			
		||||
    if (status)
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (call.get_target_temperature().has_value()) {
 | 
			
		||||
    auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature());
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status) {
 | 
			
		||||
    if (status)
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -145,9 +143,8 @@ void Anova::update() {
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status) {
 | 
			
		||||
    if (status)
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
    }
 | 
			
		||||
    this->current_request_++;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ void APDS9960::setup() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (id != 0xAB && id != 0x9C && id != 0xA8) {  // APDS9960 all should have one of these IDs
 | 
			
		||||
  if (id != 0xAB && id != 0x9C) {  // APDS9960 all should have one of these IDs
 | 
			
		||||
    this->error_code_ = WRONG_ID;
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
@@ -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))
 | 
			
		||||
 
 | 
			
		||||
@@ -53,11 +53,6 @@ service APIConnection {
 | 
			
		||||
  rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
 | 
			
		||||
  rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
 | 
			
		||||
  rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
			
		||||
 | 
			
		||||
  rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
 | 
			
		||||
 | 
			
		||||
  rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -208,14 +203,9 @@ 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;
 | 
			
		||||
 | 
			
		||||
  string friendly_name = 13;
 | 
			
		||||
 | 
			
		||||
  uint32 voice_assistant_version = 14;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message ListEntitiesRequest {
 | 
			
		||||
@@ -291,7 +281,6 @@ message ListEntitiesCoverResponse {
 | 
			
		||||
  bool disabled_by_default = 9;
 | 
			
		||||
  string icon = 10;
 | 
			
		||||
  EntityCategory entity_category = 11;
 | 
			
		||||
  bool supports_stop = 12;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum LegacyCoverState {
 | 
			
		||||
@@ -796,7 +785,6 @@ enum ClimateFanMode {
 | 
			
		||||
  CLIMATE_FAN_MIDDLE = 6;
 | 
			
		||||
  CLIMATE_FAN_FOCUS = 7;
 | 
			
		||||
  CLIMATE_FAN_DIFFUSE = 8;
 | 
			
		||||
  CLIMATE_FAN_QUIET = 9;
 | 
			
		||||
}
 | 
			
		||||
enum ClimateSwingMode {
 | 
			
		||||
  CLIMATE_SWING_OFF = 0;
 | 
			
		||||
@@ -838,7 +826,7 @@ message ListEntitiesClimateResponse {
 | 
			
		||||
  repeated ClimateMode supported_modes = 7;
 | 
			
		||||
  float visual_min_temperature = 8;
 | 
			
		||||
  float visual_max_temperature = 9;
 | 
			
		||||
  float visual_target_temperature_step = 10;
 | 
			
		||||
  float visual_temperature_step = 10;
 | 
			
		||||
  // for older peer versions - in new system this
 | 
			
		||||
  // is if CLIMATE_PRESET_AWAY exists is supported_presets
 | 
			
		||||
  bool legacy_supports_away = 11;
 | 
			
		||||
@@ -851,7 +839,6 @@ message ListEntitiesClimateResponse {
 | 
			
		||||
  bool disabled_by_default = 18;
 | 
			
		||||
  string icon = 19;
 | 
			
		||||
  EntityCategory entity_category = 20;
 | 
			
		||||
  float visual_current_temperature_step = 21;
 | 
			
		||||
}
 | 
			
		||||
message ClimateStateResponse {
 | 
			
		||||
  option (id) = 47;
 | 
			
		||||
@@ -865,7 +852,8 @@ message ClimateStateResponse {
 | 
			
		||||
  float target_temperature = 4;
 | 
			
		||||
  float target_temperature_low = 5;
 | 
			
		||||
  float target_temperature_high = 6;
 | 
			
		||||
  bool unused_legacy_away = 7;
 | 
			
		||||
  // For older peers, equal to preset == CLIMATE_PRESET_AWAY
 | 
			
		||||
  bool legacy_away = 7;
 | 
			
		||||
  ClimateAction action = 8;
 | 
			
		||||
  ClimateFanMode fan_mode = 9;
 | 
			
		||||
  ClimateSwingMode swing_mode = 10;
 | 
			
		||||
@@ -888,8 +876,9 @@ message ClimateCommandRequest {
 | 
			
		||||
  float target_temperature_low = 7;
 | 
			
		||||
  bool has_target_temperature_high = 8;
 | 
			
		||||
  float target_temperature_high = 9;
 | 
			
		||||
  bool unused_has_legacy_away = 10;
 | 
			
		||||
  bool unused_legacy_away = 11;
 | 
			
		||||
  // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
 | 
			
		||||
  bool has_legacy_away = 10;
 | 
			
		||||
  bool legacy_away = 11;
 | 
			
		||||
  bool has_fan_mode = 12;
 | 
			
		||||
  ClimateFanMode fan_mode = 13;
 | 
			
		||||
  bool has_swing_mode = 14;
 | 
			
		||||
@@ -926,7 +915,6 @@ message ListEntitiesNumberResponse {
 | 
			
		||||
  EntityCategory entity_category = 10;
 | 
			
		||||
  string unit_of_measurement = 11;
 | 
			
		||||
  NumberMode mode = 12;
 | 
			
		||||
  string device_class = 13;
 | 
			
		||||
}
 | 
			
		||||
message NumberStateResponse {
 | 
			
		||||
  option (id) = 50;
 | 
			
		||||
@@ -1132,9 +1120,6 @@ message MediaPlayerCommandRequest {
 | 
			
		||||
message SubscribeBluetoothLEAdvertisementsRequest {
 | 
			
		||||
  option (id) = 66;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint32 flags = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothServiceData {
 | 
			
		||||
@@ -1155,25 +1140,6 @@ message BluetoothLEAdvertisementResponse {
 | 
			
		||||
  repeated string service_uuids = 4;
 | 
			
		||||
  repeated BluetoothServiceData service_data = 5;
 | 
			
		||||
  repeated BluetoothServiceData manufacturer_data = 6;
 | 
			
		||||
 | 
			
		||||
  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 {
 | 
			
		||||
@@ -1181,9 +1147,6 @@ enum BluetoothDeviceRequestType {
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothDeviceRequest {
 | 
			
		||||
@@ -1193,8 +1156,6 @@ message BluetoothDeviceRequest {
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  BluetoothDeviceRequestType request_type = 2;
 | 
			
		||||
  bool has_address_type = 3;
 | 
			
		||||
  uint32 address_type = 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothDeviceConnectionResponse {
 | 
			
		||||
@@ -1367,152 +1328,3 @@ message BluetoothGATTNotifyResponse {
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothDevicePairingResponse {
 | 
			
		||||
  option (id) = 85;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  bool paired = 2;
 | 
			
		||||
  int32 error = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothDeviceUnpairingResponse {
 | 
			
		||||
  option (id) = 86;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  bool success = 2;
 | 
			
		||||
  int32 error = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message UnsubscribeBluetoothLEAdvertisementsRequest {
 | 
			
		||||
  option (id) = 87;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothDeviceClearCacheResponse {
 | 
			
		||||
  option (id) = 88;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  bool success = 2;
 | 
			
		||||
  int32 error = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== PUSH TO TALK ====================
 | 
			
		||||
message SubscribeVoiceAssistantRequest {
 | 
			
		||||
  option (id) = 89;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
			
		||||
 | 
			
		||||
  bool subscribe = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message VoiceAssistantRequest {
 | 
			
		||||
  option (id) = 90;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
			
		||||
 | 
			
		||||
  bool start = 1;
 | 
			
		||||
  string conversation_id = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message VoiceAssistantResponse {
 | 
			
		||||
  option (id) = 91;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
			
		||||
 | 
			
		||||
  uint32 port = 1;
 | 
			
		||||
  bool error = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum VoiceAssistantEvent {
 | 
			
		||||
  VOICE_ASSISTANT_ERROR = 0;
 | 
			
		||||
  VOICE_ASSISTANT_RUN_START = 1;
 | 
			
		||||
  VOICE_ASSISTANT_RUN_END = 2;
 | 
			
		||||
  VOICE_ASSISTANT_STT_START = 3;
 | 
			
		||||
  VOICE_ASSISTANT_STT_END = 4;
 | 
			
		||||
  VOICE_ASSISTANT_INTENT_START = 5;
 | 
			
		||||
  VOICE_ASSISTANT_INTENT_END = 6;
 | 
			
		||||
  VOICE_ASSISTANT_TTS_START = 7;
 | 
			
		||||
  VOICE_ASSISTANT_TTS_END = 8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message VoiceAssistantEventData {
 | 
			
		||||
  string name = 1;
 | 
			
		||||
  string value = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message VoiceAssistantEventResponse {
 | 
			
		||||
  option (id) = 92;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
#include "api_connection.h"
 | 
			
		||||
#include <cerrno>
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
#include "esphome/core/entity_base.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
@@ -16,9 +15,6 @@
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
#include "esphome/components/voice_assistant/voice_assistant.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
@@ -51,14 +47,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;
 | 
			
		||||
@@ -192,8 +180,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
 | 
			
		||||
  ListEntitiesBinarySensorResponse msg;
 | 
			
		||||
  msg.object_id = binary_sensor->get_object_id();
 | 
			
		||||
  msg.key = binary_sensor->get_object_id_hash();
 | 
			
		||||
  if (binary_sensor->has_own_name())
 | 
			
		||||
    msg.name = binary_sensor->get_name();
 | 
			
		||||
  msg.name = binary_sensor->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor);
 | 
			
		||||
  msg.device_class = binary_sensor->get_device_class();
 | 
			
		||||
  msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
 | 
			
		||||
@@ -225,13 +212,11 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
 | 
			
		||||
  ListEntitiesCoverResponse msg;
 | 
			
		||||
  msg.key = cover->get_object_id_hash();
 | 
			
		||||
  msg.object_id = cover->get_object_id();
 | 
			
		||||
  if (cover->has_own_name())
 | 
			
		||||
    msg.name = cover->get_name();
 | 
			
		||||
  msg.name = cover->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("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();
 | 
			
		||||
@@ -290,8 +275,7 @@ bool APIConnection::send_fan_info(fan::Fan *fan) {
 | 
			
		||||
  ListEntitiesFanResponse msg;
 | 
			
		||||
  msg.key = fan->get_object_id_hash();
 | 
			
		||||
  msg.object_id = fan->get_object_id();
 | 
			
		||||
  if (fan->has_own_name())
 | 
			
		||||
    msg.name = fan->get_name();
 | 
			
		||||
  msg.name = fan->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("fan", fan);
 | 
			
		||||
  msg.supports_oscillation = traits.supports_oscillation();
 | 
			
		||||
  msg.supports_speed = traits.supports_speed();
 | 
			
		||||
@@ -353,8 +337,7 @@ bool APIConnection::send_light_info(light::LightState *light) {
 | 
			
		||||
  ListEntitiesLightResponse msg;
 | 
			
		||||
  msg.key = light->get_object_id_hash();
 | 
			
		||||
  msg.object_id = light->get_object_id();
 | 
			
		||||
  if (light->has_own_name())
 | 
			
		||||
    msg.name = light->get_name();
 | 
			
		||||
  msg.name = light->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("light", light);
 | 
			
		||||
 | 
			
		||||
  msg.disabled_by_default = light->is_disabled_by_default();
 | 
			
		||||
@@ -435,8 +418,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
 | 
			
		||||
  ListEntitiesSensorResponse msg;
 | 
			
		||||
  msg.key = sensor->get_object_id_hash();
 | 
			
		||||
  msg.object_id = sensor->get_object_id();
 | 
			
		||||
  if (sensor->has_own_name())
 | 
			
		||||
    msg.name = sensor->get_name();
 | 
			
		||||
  msg.name = sensor->get_name();
 | 
			
		||||
  msg.unique_id = sensor->unique_id();
 | 
			
		||||
  if (msg.unique_id.empty())
 | 
			
		||||
    msg.unique_id = get_default_unique_id("sensor", sensor);
 | 
			
		||||
@@ -466,8 +448,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
 | 
			
		||||
  ListEntitiesSwitchResponse msg;
 | 
			
		||||
  msg.key = a_switch->get_object_id_hash();
 | 
			
		||||
  msg.object_id = a_switch->get_object_id();
 | 
			
		||||
  if (a_switch->has_own_name())
 | 
			
		||||
    msg.name = a_switch->get_name();
 | 
			
		||||
  msg.name = a_switch->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("switch", a_switch);
 | 
			
		||||
  msg.icon = a_switch->get_icon();
 | 
			
		||||
  msg.assumed_state = a_switch->assumed_state();
 | 
			
		||||
@@ -539,6 +520,7 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
 | 
			
		||||
    resp.custom_fan_mode = climate->custom_fan_mode.value();
 | 
			
		||||
  if (traits.get_supports_presets() && climate->preset.has_value()) {
 | 
			
		||||
    resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
 | 
			
		||||
    resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY;
 | 
			
		||||
  }
 | 
			
		||||
  if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value())
 | 
			
		||||
    resp.custom_preset = climate->custom_preset.value();
 | 
			
		||||
@@ -551,8 +533,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
 | 
			
		||||
  ListEntitiesClimateResponse msg;
 | 
			
		||||
  msg.key = climate->get_object_id_hash();
 | 
			
		||||
  msg.object_id = climate->get_object_id();
 | 
			
		||||
  if (climate->has_own_name())
 | 
			
		||||
    msg.name = climate->get_name();
 | 
			
		||||
  msg.name = climate->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("climate", climate);
 | 
			
		||||
 | 
			
		||||
  msg.disabled_by_default = climate->is_disabled_by_default();
 | 
			
		||||
@@ -567,9 +548,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
 | 
			
		||||
 | 
			
		||||
  msg.visual_min_temperature = traits.get_visual_min_temperature();
 | 
			
		||||
  msg.visual_max_temperature = traits.get_visual_max_temperature();
 | 
			
		||||
  msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
 | 
			
		||||
  msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
 | 
			
		||||
 | 
			
		||||
  msg.visual_temperature_step = traits.get_visual_temperature_step();
 | 
			
		||||
  msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
 | 
			
		||||
  msg.supports_action = traits.get_supports_action();
 | 
			
		||||
 | 
			
		||||
@@ -599,6 +578,8 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
 | 
			
		||||
    call.set_target_temperature_low(msg.target_temperature_low);
 | 
			
		||||
  if (msg.has_target_temperature_high)
 | 
			
		||||
    call.set_target_temperature_high(msg.target_temperature_high);
 | 
			
		||||
  if (msg.has_legacy_away)
 | 
			
		||||
    call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME);
 | 
			
		||||
  if (msg.has_fan_mode)
 | 
			
		||||
    call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
 | 
			
		||||
  if (msg.has_custom_fan_mode)
 | 
			
		||||
@@ -628,15 +609,13 @@ bool APIConnection::send_number_info(number::Number *number) {
 | 
			
		||||
  ListEntitiesNumberResponse msg;
 | 
			
		||||
  msg.key = number->get_object_id_hash();
 | 
			
		||||
  msg.object_id = number->get_object_id();
 | 
			
		||||
  if (number->has_own_name())
 | 
			
		||||
    msg.name = number->get_name();
 | 
			
		||||
  msg.name = number->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("number", number);
 | 
			
		||||
  msg.icon = number->get_icon();
 | 
			
		||||
  msg.disabled_by_default = number->is_disabled_by_default();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category());
 | 
			
		||||
  msg.unit_of_measurement = number->traits.get_unit_of_measurement();
 | 
			
		||||
  msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
 | 
			
		||||
  msg.device_class = number->traits.get_device_class();
 | 
			
		||||
 | 
			
		||||
  msg.min_value = number->traits.get_min_value();
 | 
			
		||||
  msg.max_value = number->traits.get_max_value();
 | 
			
		||||
@@ -670,8 +649,7 @@ bool APIConnection::send_select_info(select::Select *select) {
 | 
			
		||||
  ListEntitiesSelectResponse msg;
 | 
			
		||||
  msg.key = select->get_object_id_hash();
 | 
			
		||||
  msg.object_id = select->get_object_id();
 | 
			
		||||
  if (select->has_own_name())
 | 
			
		||||
    msg.name = select->get_name();
 | 
			
		||||
  msg.name = select->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("select", select);
 | 
			
		||||
  msg.icon = select->get_icon();
 | 
			
		||||
  msg.disabled_by_default = select->is_disabled_by_default();
 | 
			
		||||
@@ -698,8 +676,7 @@ bool APIConnection::send_button_info(button::Button *button) {
 | 
			
		||||
  ListEntitiesButtonResponse msg;
 | 
			
		||||
  msg.key = button->get_object_id_hash();
 | 
			
		||||
  msg.object_id = button->get_object_id();
 | 
			
		||||
  if (button->has_own_name())
 | 
			
		||||
    msg.name = button->get_name();
 | 
			
		||||
  msg.name = button->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("button", button);
 | 
			
		||||
  msg.icon = button->get_icon();
 | 
			
		||||
  msg.disabled_by_default = button->is_disabled_by_default();
 | 
			
		||||
@@ -730,8 +707,7 @@ bool APIConnection::send_lock_info(lock::Lock *a_lock) {
 | 
			
		||||
  ListEntitiesLockResponse msg;
 | 
			
		||||
  msg.key = a_lock->get_object_id_hash();
 | 
			
		||||
  msg.object_id = a_lock->get_object_id();
 | 
			
		||||
  if (a_lock->has_own_name())
 | 
			
		||||
    msg.name = a_lock->get_name();
 | 
			
		||||
  msg.name = a_lock->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("lock", a_lock);
 | 
			
		||||
  msg.icon = a_lock->get_icon();
 | 
			
		||||
  msg.assumed_state = a_lock->traits.get_assumed_state();
 | 
			
		||||
@@ -776,8 +752,7 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play
 | 
			
		||||
  ListEntitiesMediaPlayerResponse msg;
 | 
			
		||||
  msg.key = media_player->get_object_id_hash();
 | 
			
		||||
  msg.object_id = media_player->get_object_id();
 | 
			
		||||
  if (media_player->has_own_name())
 | 
			
		||||
    msg.name = media_player->get_name();
 | 
			
		||||
  msg.name = media_player->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("media_player", media_player);
 | 
			
		||||
  msg.icon = media_player->get_icon();
 | 
			
		||||
  msg.disabled_by_default = media_player->is_disabled_by_default();
 | 
			
		||||
@@ -821,8 +796,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
 | 
			
		||||
  ListEntitiesCameraResponse msg;
 | 
			
		||||
  msg.key = camera->get_object_id_hash();
 | 
			
		||||
  msg.object_id = camera->get_object_id();
 | 
			
		||||
  if (camera->has_own_name())
 | 
			
		||||
    msg.name = camera->get_name();
 | 
			
		||||
  msg.name = camera->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("camera", camera);
 | 
			
		||||
  msg.disabled_by_default = camera->is_disabled_by_default();
 | 
			
		||||
  msg.icon = camera->get_icon();
 | 
			
		||||
@@ -853,13 +827,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) {
 | 
			
		||||
@@ -906,89 +876,6 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
bool APIConnection::request_voice_assistant(bool start, const std::string &conversation_id) {
 | 
			
		||||
  if (!this->voice_assistant_subscription_)
 | 
			
		||||
    return false;
 | 
			
		||||
  VoiceAssistantRequest msg;
 | 
			
		||||
  msg.start = start;
 | 
			
		||||
  msg.conversation_id = conversation_id;
 | 
			
		||||
  return this->send_voice_assistant_request(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
 | 
			
		||||
  if (voice_assistant::global_voice_assistant != nullptr) {
 | 
			
		||||
    struct sockaddr_storage storage;
 | 
			
		||||
    socklen_t len = sizeof(storage);
 | 
			
		||||
    this->helper_->getpeername((struct sockaddr *) &storage, &len);
 | 
			
		||||
    voice_assistant::global_voice_assistant->start(&storage, msg.port);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {
 | 
			
		||||
  if (voice_assistant::global_voice_assistant != nullptr) {
 | 
			
		||||
    voice_assistant::global_voice_assistant->on_event(msg);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#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;
 | 
			
		||||
@@ -1008,12 +895,12 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
 | 
			
		||||
  this->helper_->set_log_info(client_info_);
 | 
			
		||||
  this->client_api_version_major_ = msg.api_version_major;
 | 
			
		||||
  this->client_api_version_minor_ = msg.api_version_minor;
 | 
			
		||||
  ESP_LOGV(TAG, "Hello from client: '%s' | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(),
 | 
			
		||||
  ESP_LOGV(TAG, "Hello from client: '%s' | API Version %d.%d", this->client_info_.c_str(),
 | 
			
		||||
           this->client_api_version_major_, this->client_api_version_minor_);
 | 
			
		||||
 | 
			
		||||
  HelloResponse resp;
 | 
			
		||||
  resp.api_version_major = 1;
 | 
			
		||||
  resp.api_version_minor = 9;
 | 
			
		||||
  resp.api_version_minor = 7;
 | 
			
		||||
  resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
 | 
			
		||||
  resp.name = App.get_name();
 | 
			
		||||
 | 
			
		||||
@@ -1042,7 +929,6 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
 | 
			
		||||
  DeviceInfoResponse resp{};
 | 
			
		||||
  resp.uses_password = this->parent_->uses_password();
 | 
			
		||||
  resp.name = App.get_name();
 | 
			
		||||
  resp.friendly_name = App.get_friendly_name();
 | 
			
		||||
  resp.mac_address = get_mac_address_pretty();
 | 
			
		||||
  resp.esphome_version = ESPHOME_VERSION;
 | 
			
		||||
  resp.compilation_time = App.get_compilation_time();
 | 
			
		||||
@@ -1050,8 +936,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
 | 
			
		||||
@@ -1065,11 +949,7 @@ 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();
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  resp.voice_assistant_version = voice_assistant::global_voice_assistant->get_version();
 | 
			
		||||
  resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 2 : 1;
 | 
			
		||||
#endif
 | 
			
		||||
  return resp;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,6 @@
 | 
			
		||||
#include "api_server.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
@@ -16,7 +13,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 +95,6 @@ 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;
 | 
			
		||||
  bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg);
 | 
			
		||||
 | 
			
		||||
  void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
 | 
			
		||||
@@ -120,21 +115,6 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  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);
 | 
			
		||||
  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
 | 
			
		||||
@@ -168,7 +148,9 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
  void execute_service(const ExecuteServiceRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
  void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override {
 | 
			
		||||
    this->bluetooth_le_advertisement_subscription_ = true;
 | 
			
		||||
  }
 | 
			
		||||
  bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
 | 
			
		||||
  bool is_connection_setup() override {
 | 
			
		||||
    return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated();
 | 
			
		||||
@@ -213,9 +195,7 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  uint32_t last_traffic_;
 | 
			
		||||
  bool sent_ping_{false};
 | 
			
		||||
  bool service_call_subscription_{false};
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  bool voice_assistant_subscription_{false};
 | 
			
		||||
#endif
 | 
			
		||||
  bool bluetooth_le_advertisement_subscription_{false};
 | 
			
		||||
  bool next_close_ = false;
 | 
			
		||||
  APIServer *parent_;
 | 
			
		||||
  InitialStateIterator initial_state_iterator_;
 | 
			
		||||
 
 | 
			
		||||
@@ -295,7 +295,7 @@ APIError APINoiseFrameHelper::state_action_() {
 | 
			
		||||
    if (aerr != APIError::OK)
 | 
			
		||||
      return aerr;
 | 
			
		||||
    // ignore contents, may be used in future for flags
 | 
			
		||||
    prologue_.push_back((uint8_t) (frame.msg.size() >> 8));
 | 
			
		||||
    prologue_.push_back((uint8_t)(frame.msg.size() >> 8));
 | 
			
		||||
    prologue_.push_back((uint8_t) frame.msg.size());
 | 
			
		||||
    prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
 | 
			
		||||
 | 
			
		||||
@@ -492,9 +492,9 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload
 | 
			
		||||
  // tmpbuf[1], tmpbuf[2] to be set later
 | 
			
		||||
  const uint8_t msg_offset = 3;
 | 
			
		||||
  const uint8_t payload_offset = msg_offset + 4;
 | 
			
		||||
  tmpbuf[msg_offset + 0] = (uint8_t) (type >> 8);  // type
 | 
			
		||||
  tmpbuf[msg_offset + 0] = (uint8_t)(type >> 8);  // type
 | 
			
		||||
  tmpbuf[msg_offset + 1] = (uint8_t) type;
 | 
			
		||||
  tmpbuf[msg_offset + 2] = (uint8_t) (payload_len >> 8);  // data_len
 | 
			
		||||
  tmpbuf[msg_offset + 2] = (uint8_t)(payload_len >> 8);  // data_len
 | 
			
		||||
  tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
 | 
			
		||||
  // copy data
 | 
			
		||||
  std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
 | 
			
		||||
@@ -512,7 +512,7 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  size_t total_len = 3 + mbuf.size;
 | 
			
		||||
  tmpbuf[1] = (uint8_t) (mbuf.size >> 8);
 | 
			
		||||
  tmpbuf[1] = (uint8_t)(mbuf.size >> 8);
 | 
			
		||||
  tmpbuf[2] = (uint8_t) mbuf.size;
 | 
			
		||||
 | 
			
		||||
  struct iovec iov;
 | 
			
		||||
@@ -610,15 +610,12 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
 | 
			
		||||
  uint8_t header[3];
 | 
			
		||||
  header[0] = 0x01;  // indicator
 | 
			
		||||
  header[1] = (uint8_t) (len >> 8);
 | 
			
		||||
  header[1] = (uint8_t)(len >> 8);
 | 
			
		||||
  header[2] = (uint8_t) len;
 | 
			
		||||
 | 
			
		||||
  struct iovec iov[2];
 | 
			
		||||
  iov[0].iov_base = header;
 | 
			
		||||
  iov[0].iov_len = 3;
 | 
			
		||||
  if (len == 0) {
 | 
			
		||||
    return write_raw_(iov, 1);
 | 
			
		||||
  }
 | 
			
		||||
  iov[1].iov_base = const_cast<uint8_t *>(data);
 | 
			
		||||
  iov[1].iov_len = len;
 | 
			
		||||
 | 
			
		||||
@@ -916,9 +913,6 @@ APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *pay
 | 
			
		||||
  struct iovec iov[2];
 | 
			
		||||
  iov[0].iov_base = &header[0];
 | 
			
		||||
  iov[0].iov_len = header.size();
 | 
			
		||||
  if (payload_len == 0) {
 | 
			
		||||
    return write_raw_(iov, 1);
 | 
			
		||||
  }
 | 
			
		||||
  iov[1].iov_base = const_cast<uint8_t *>(payload);
 | 
			
		||||
  iov[1].iov_len = payload_len;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,8 @@
 | 
			
		||||
#include "noise/protocol.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "api_noise_context.h"
 | 
			
		||||
#include "esphome/components/socket/socket.h"
 | 
			
		||||
#include "api_noise_context.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
@@ -67,7 +67,6 @@ class APIFrameHelper {
 | 
			
		||||
  virtual bool can_write_without_blocking() = 0;
 | 
			
		||||
  virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
 | 
			
		||||
  virtual std::string getpeername() = 0;
 | 
			
		||||
  virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
 | 
			
		||||
  virtual APIError close() = 0;
 | 
			
		||||
  virtual APIError shutdown(int how) = 0;
 | 
			
		||||
  // Give this helper a name for logging
 | 
			
		||||
@@ -85,10 +84,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
 | 
			
		||||
  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
			
		||||
  bool can_write_without_blocking() override;
 | 
			
		||||
  APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
 | 
			
		||||
  std::string getpeername() override { return this->socket_->getpeername(); }
 | 
			
		||||
  int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
 | 
			
		||||
    return this->socket_->getpeername(addr, addrlen);
 | 
			
		||||
  }
 | 
			
		||||
  std::string getpeername() override { return socket_->getpeername(); }
 | 
			
		||||
  APIError close() override;
 | 
			
		||||
  APIError shutdown(int how) override;
 | 
			
		||||
  // Give this helper a name for logging
 | 
			
		||||
@@ -148,10 +144,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
			
		||||
  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
			
		||||
  bool can_write_without_blocking() override;
 | 
			
		||||
  APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
 | 
			
		||||
  std::string getpeername() override { return this->socket_->getpeername(); }
 | 
			
		||||
  int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
 | 
			
		||||
    return this->socket_->getpeername(addr, addrlen);
 | 
			
		||||
  }
 | 
			
		||||
  std::string getpeername() override { return socket_->getpeername(); }
 | 
			
		||||
  APIError close() override;
 | 
			
		||||
  APIError shutdown(int how) override;
 | 
			
		||||
  // Give this helper a name for logging
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user