mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 00:21:56 +00:00 
			
		
		
		
	Compare commits
	
		
			101 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					53a4689ed1 | ||
| 
						 | 
					0a82e6e792 | ||
| 
						 | 
					98855e4123 | ||
| 
						 | 
					6a09d7c49b | ||
| 
						 | 
					46e50ba53f | ||
| 
						 | 
					f1e3ff2ed2 | ||
| 
						 | 
					7787fa8f29 | ||
| 
						 | 
					70902029f8 | ||
| 
						 | 
					4f9a56c884 | ||
| 
						 | 
					3715ba030b | ||
| 
						 | 
					0c93be97a9 | ||
| 
						 | 
					54eb6070fb | ||
| 
						 | 
					4dbf1c521e | ||
| 
						 | 
					f30b8f6b3c | ||
| 
						 | 
					18c08f24ad | ||
| 
						 | 
					a7f53aea0e | ||
| 
						 | 
					c399905675 | ||
| 
						 | 
					5cb0c11feb | ||
| 
						 | 
					08b67e7aea | ||
| 
						 | 
					07ae8ec553 | ||
| 
						 | 
					04c3a43c17 | ||
| 
						 | 
					b632344596 | ||
| 
						 | 
					fb8ec79a52 | ||
| 
						 | 
					7dd16df846 | ||
| 
						 | 
					551e9c6111 | ||
| 
						 | 
					cc9f0b3f47 | ||
| 
						 | 
					d77c3abdc0 | ||
| 
						 | 
					dd37a4e04c | ||
| 
						 | 
					1f5c79bd17 | ||
| 
						 | 
					623570a117 | ||
| 
						 | 
					cdbc146e5d | ||
| 
						 | 
					7ae611256a | ||
| 
						 | 
					b62c47fede | ||
| 
						 | 
					99f497c3b8 | ||
| 
						 | 
					4f88c2489b | ||
| 
						 | 
					294ba1fca7 | ||
| 
						 | 
					be61b38a2c | ||
| 
						 | 
					f9797825ad | ||
| 
						 | 
					fd4b7d4588 | ||
| 
						 | 
					062cedc200 | ||
| 
						 | 
					79b9d0579d | ||
| 
						 | 
					ab31117bf3 | ||
| 
						 | 
					d31040f5d8 | ||
| 
						 | 
					52d19fa43d | ||
| 
						 | 
					8ca34f7098 | ||
| 
						 | 
					4c4099966a | ||
| 
						 | 
					86ac7f3a59 | ||
| 
						 | 
					9e400a7857 | ||
| 
						 | 
					d5278351da | ||
| 
						 | 
					36861595f1 | ||
| 
						 | 
					d604321f37 | ||
| 
						 | 
					964ab65497 | ||
| 
						 | 
					3b940b1c04 | ||
| 
						 | 
					5fca480921 | ||
| 
						 | 
					7051f897bc | ||
| 
						 | 
					2cb3015a28 | ||
| 
						 | 
					d0859a7d33 | ||
| 
						 | 
					61ebc629f6 | ||
| 
						 | 
					32f2da77f8 | ||
| 
						 | 
					bfca3f242a | ||
| 
						 | 
					3dfff2930a | ||
| 
						 | 
					027e0de48e | ||
| 
						 | 
					c811141a4f | ||
| 
						 | 
					871c0ee2a5 | ||
| 
						 | 
					b8a7741c61 | ||
| 
						 | 
					b6011b9353 | ||
| 
						 | 
					40a5005d94 | ||
| 
						 | 
					c5eba21ff6 | ||
| 
						 | 
					4891cfef56 | ||
| 
						 | 
					4395664547 | ||
| 
						 | 
					04d926af39 | ||
| 
						 | 
					f9a31c1abb | ||
| 
						 | 
					dca1c0f160 | ||
| 
						 | 
					2419bc3678 | ||
| 
						 | 
					c19b3ecd43 | ||
| 
						 | 
					ef1e91d838 | ||
| 
						 | 
					e5929225eb | ||
| 
						 | 
					607c3ae651 | ||
| 
						 | 
					5591832b50 | ||
| 
						 | 
					25b116048c | ||
| 
						 | 
					24ba9eba46 | ||
| 
						 | 
					424c34225f | ||
| 
						 | 
					d781f3a11b | ||
| 
						 | 
					a80f9ed336 | ||
| 
						 | 
					92bbedfa5a | ||
| 
						 | 
					86710ed483 | ||
| 
						 | 
					da7eb9ac90 | ||
| 
						 | 
					c411043681 | ||
| 
						 | 
					93f8ee7e60 | ||
| 
						 | 
					0efc1f06f2 | ||
| 
						 | 
					9ad9d64ac7 | ||
| 
						 | 
					5a2cfa2798 | ||
| 
						 | 
					eb24da7c82 | ||
| 
						 | 
					f93e261d75 | ||
| 
						 | 
					501f88ca86 | ||
| 
						 | 
					360effcb72 | ||
| 
						 | 
					eb9bd69405 | ||
| 
						 | 
					11b8210e36 | ||
| 
						 | 
					d23376b81e | ||
| 
						 | 
					99d90845b5 | ||
| 
						 | 
					ea0127e42b | 
@@ -49,7 +49,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
 | 
			
		||||
ConstructorInitializerIndentWidth: 4
 | 
			
		||||
ContinuationIndentWidth: 4
 | 
			
		||||
Cpp11BracedListStyle: true
 | 
			
		||||
DerivePointerAlignment: true
 | 
			
		||||
DerivePointerAlignment: false
 | 
			
		||||
DisableFormat:   false
 | 
			
		||||
ExperimentalAutoDetectBinPacking: false
 | 
			
		||||
FixNamespaceComments: true
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								.clang-tidy
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								.clang-tidy
									
									
									
									
									
								
							@@ -5,30 +5,19 @@ Checks: >-
 | 
			
		||||
  -android-*,
 | 
			
		||||
  -boost-*,
 | 
			
		||||
  -bugprone-branch-clone,
 | 
			
		||||
  -bugprone-macro-parentheses,
 | 
			
		||||
  -bugprone-narrowing-conversions,
 | 
			
		||||
  -bugprone-reserved-identifier,
 | 
			
		||||
  -bugprone-signed-char-misuse,
 | 
			
		||||
  -bugprone-suspicious-include,
 | 
			
		||||
  -bugprone-too-small-loop-variable,
 | 
			
		||||
  -bugprone-unhandled-self-assignment,
 | 
			
		||||
  -cert-dcl37-c,
 | 
			
		||||
  -cert-dcl50-cpp,
 | 
			
		||||
  -cert-dcl51-cpp,
 | 
			
		||||
  -cert-err58-cpp,
 | 
			
		||||
  -cert-oop54-cpp,
 | 
			
		||||
  -cert-oop57-cpp,
 | 
			
		||||
  -cert-str34-c,
 | 
			
		||||
  -clang-analyzer-core.CallAndMessage,
 | 
			
		||||
  -clang-analyzer-optin.*,
 | 
			
		||||
  -clang-analyzer-optin.cplusplus.UninitializedObject,
 | 
			
		||||
  -clang-analyzer-osx.*,
 | 
			
		||||
  -clang-analyzer-security.*,
 | 
			
		||||
  -clang-diagnostic-shadow-field,
 | 
			
		||||
  -cppcoreguidelines-avoid-c-arrays,
 | 
			
		||||
  -cppcoreguidelines-avoid-goto,
 | 
			
		||||
  -cppcoreguidelines-avoid-magic-numbers,
 | 
			
		||||
  -cppcoreguidelines-avoid-non-const-global-variables,
 | 
			
		||||
  -cppcoreguidelines-c-copy-assignment-signature,
 | 
			
		||||
  -cppcoreguidelines-init-variables,
 | 
			
		||||
  -cppcoreguidelines-macro-usage,
 | 
			
		||||
  -cppcoreguidelines-narrowing-conversions,
 | 
			
		||||
@@ -45,17 +34,17 @@ Checks: >-
 | 
			
		||||
  -cppcoreguidelines-pro-type-union-access,
 | 
			
		||||
  -cppcoreguidelines-pro-type-vararg,
 | 
			
		||||
  -cppcoreguidelines-special-member-functions,
 | 
			
		||||
  -fuchsia-*,
 | 
			
		||||
  -fuchsia-default-arguments,
 | 
			
		||||
  -fuchsia-multiple-inheritance,
 | 
			
		||||
  -fuchsia-overloaded-operator,
 | 
			
		||||
  -fuchsia-statically-constructed-objects,
 | 
			
		||||
  -fuchsia-default-arguments-declarations,
 | 
			
		||||
  -fuchsia-default-arguments-calls,
 | 
			
		||||
  -google-build-using-namespace,
 | 
			
		||||
  -google-explicit-constructor,
 | 
			
		||||
  -google-readability-braces-around-statements,
 | 
			
		||||
  -google-readability-casting,
 | 
			
		||||
  -google-readability-todo,
 | 
			
		||||
  -google-runtime-int,
 | 
			
		||||
  -google-runtime-references,
 | 
			
		||||
  -hicpp-*,
 | 
			
		||||
  -llvm-else-after-return,
 | 
			
		||||
@@ -65,12 +54,8 @@ Checks: >-
 | 
			
		||||
  -llvmlibc-*,
 | 
			
		||||
  -misc-non-private-member-variables-in-classes,
 | 
			
		||||
  -misc-no-recursion,
 | 
			
		||||
  -misc-unconventional-assign-operator,
 | 
			
		||||
  -misc-unused-parameters,
 | 
			
		||||
  -modernize-avoid-c-arrays,
 | 
			
		||||
  -modernize-deprecated-headers,
 | 
			
		||||
  -modernize-pass-by-value,
 | 
			
		||||
  -modernize-pass-by-value,
 | 
			
		||||
  -modernize-return-braced-init-list,
 | 
			
		||||
  -modernize-use-auto,
 | 
			
		||||
  -modernize-use-default-member-init,
 | 
			
		||||
@@ -78,7 +63,6 @@ Checks: >-
 | 
			
		||||
  -modernize-use-trailing-return-type,
 | 
			
		||||
  -mpi-*,
 | 
			
		||||
  -objc-*,
 | 
			
		||||
  -performance-unnecessary-value-param,
 | 
			
		||||
  -readability-braces-around-statements,
 | 
			
		||||
  -readability-const-return-type,
 | 
			
		||||
  -readability-convert-member-functions-to-static,
 | 
			
		||||
@@ -94,8 +78,7 @@ Checks: >-
 | 
			
		||||
  -readability-redundant-string-init,
 | 
			
		||||
  -readability-uppercase-literal-suffix,
 | 
			
		||||
  -readability-use-anyofallof,
 | 
			
		||||
  -warnings-as-errors,
 | 
			
		||||
  -zircon-*
 | 
			
		||||
  -warnings-as-errors
 | 
			
		||||
WarningsAsErrors: '*'
 | 
			
		||||
HeaderFilterRegex: '^.*/src/esphome/.*'
 | 
			
		||||
AnalyzeTemporaryDtors: false
 | 
			
		||||
 
 | 
			
		||||
@@ -2,16 +2,29 @@
 | 
			
		||||
  "name": "ESPHome Dev",
 | 
			
		||||
  "context": "..",
 | 
			
		||||
  "dockerFile": "../docker/Dockerfile.dev",
 | 
			
		||||
  "postCreateCommand": "mkdir -p config && pip3 install -e .",
 | 
			
		||||
  "runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"],
 | 
			
		||||
  "postCreateCommand": [
 | 
			
		||||
    "script/devcontainer-post-create"
 | 
			
		||||
  ],
 | 
			
		||||
  "runArgs": [
 | 
			
		||||
    "--privileged",
 | 
			
		||||
    "-e",
 | 
			
		||||
    "ESPHOME_DASHBOARD_USE_PING=1"
 | 
			
		||||
  ],
 | 
			
		||||
  "appPort": 6052,
 | 
			
		||||
  "extensions": [
 | 
			
		||||
    // python
 | 
			
		||||
    "ms-python.python",
 | 
			
		||||
    "visualstudioexptteam.vscodeintellicode",
 | 
			
		||||
    "redhat.vscode-yaml"
 | 
			
		||||
    // yaml
 | 
			
		||||
    "redhat.vscode-yaml",
 | 
			
		||||
    // cpp
 | 
			
		||||
    "ms-vscode.cpptools",
 | 
			
		||||
    // editorconfig
 | 
			
		||||
    "editorconfig.editorconfig",
 | 
			
		||||
  ],
 | 
			
		||||
  "settings": {
 | 
			
		||||
    "python.pythonPath": "/usr/local/bin/python",
 | 
			
		||||
    "python.languageServer": "Pylance",
 | 
			
		||||
    "python.pythonPath": "/usr/bin/python3",
 | 
			
		||||
    "python.linting.pylintEnabled": true,
 | 
			
		||||
    "python.linting.enabled": true,
 | 
			
		||||
    "python.formatting.provider": "black",
 | 
			
		||||
@@ -19,7 +32,7 @@
 | 
			
		||||
    "editor.formatOnSave": true,
 | 
			
		||||
    "editor.formatOnType": true,
 | 
			
		||||
    "files.trimTrailingWhitespace": true,
 | 
			
		||||
    "terminal.integrated.shell.linux": "/bin/bash",
 | 
			
		||||
    "terminal.integrated.defaultProfile.linux": "bash",
 | 
			
		||||
    "yaml.customTags": [
 | 
			
		||||
      "!secret scalar",
 | 
			
		||||
      "!lambda scalar",
 | 
			
		||||
@@ -27,6 +40,18 @@
 | 
			
		||||
      "!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",
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -103,6 +103,10 @@ venv.bak/
 | 
			
		||||
# mypy
 | 
			
		||||
.mypy_cache/
 | 
			
		||||
 | 
			
		||||
# PlatformIO
 | 
			
		||||
.pio/
 | 
			
		||||
 | 
			
		||||
# ESPHome
 | 
			
		||||
config/
 | 
			
		||||
examples/
 | 
			
		||||
Dockerfile
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ insert_final_newline = true
 | 
			
		||||
charset = utf-8
 | 
			
		||||
 | 
			
		||||
# python
 | 
			
		||||
[*.{py}]
 | 
			
		||||
[*.py]
 | 
			
		||||
indent_style = space
 | 
			
		||||
indent_size = 4
 | 
			
		||||
 | 
			
		||||
@@ -25,4 +25,10 @@ indent_size = 2
 | 
			
		||||
[*.{yaml,yml}]
 | 
			
		||||
indent_style = space
 | 
			
		||||
indent_size = 2
 | 
			
		||||
quote_type = single
 | 
			
		||||
quote_type = single
 | 
			
		||||
 | 
			
		||||
# JSON
 | 
			
		||||
[*.json]
 | 
			
		||||
indent_style = space
 | 
			
		||||
indent_size = 2
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										49
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										49
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -3,7 +3,7 @@ name: CI for docker images
 | 
			
		||||
# Only run when docker paths change
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches: [dev, beta, master]
 | 
			
		||||
    branches: [dev, beta, release]
 | 
			
		||||
    paths:
 | 
			
		||||
      - 'docker/**'
 | 
			
		||||
      - '.github/workflows/**'
 | 
			
		||||
@@ -18,38 +18,23 @@ jobs:
 | 
			
		||||
    name: Build docker containers
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        build_type: ["hassio", "docker"]
 | 
			
		||||
        build_type: ["ha-addon", "docker", "lint"]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up env variables
 | 
			
		||||
        run: |
 | 
			
		||||
          base_version="3.4.0"
 | 
			
		||||
    - uses: actions/checkout@v2
 | 
			
		||||
    - name: Set up Python
 | 
			
		||||
      uses: actions/setup-python@v2
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: '3.9'
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        echo "TAG=check" >> $GITHUB_ENV
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
			
		||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
            build_to="esphome/esphome-hassio-${{ matrix.arch }}"
 | 
			
		||||
            dockerfile="docker/Dockerfile.hassio"
 | 
			
		||||
          else
 | 
			
		||||
            build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
            build_to="esphome/esphome-${{ matrix.arch }}"
 | 
			
		||||
            dockerfile="docker/Dockerfile"
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
          echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
 | 
			
		||||
          echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
 | 
			
		||||
          echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Pull for cache
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull "${BUILD_TO}:dev" || true
 | 
			
		||||
      - name: Register QEMU binfmt
 | 
			
		||||
        run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
 | 
			
		||||
      - run: |
 | 
			
		||||
          docker build \
 | 
			
		||||
            --build-arg "BUILD_FROM=${BUILD_FROM}" \
 | 
			
		||||
            --build-arg "BUILD_VERSION=ci" \
 | 
			
		||||
            --cache-from "${BUILD_TO}:dev" \
 | 
			
		||||
            --file "${DOCKERFILE}" \
 | 
			
		||||
            .
 | 
			
		||||
    - name: Run build
 | 
			
		||||
      run: |
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${TAG}" \
 | 
			
		||||
          --arch "${{ matrix.arch }}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          build
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										178
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										178
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,40 +4,36 @@ name: CI
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    # On dev branch release-dev already performs CI checks
 | 
			
		||||
    # On other branches the `pull_request` trigger will be used
 | 
			
		||||
    branches: [beta, master]
 | 
			
		||||
    branches: [dev, beta, release]
 | 
			
		||||
 | 
			
		||||
  pull_request:
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  lint-clang-format:
 | 
			
		||||
  ci-with-container:
 | 
			
		||||
    name: ${{ matrix.name }}
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: esphome/esphome-lint:1.1
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
        run: pio init --ide atom
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-format
 | 
			
		||||
        run: script/clang-format -i
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-clang-tidy:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: esphome/esphome-lint:1.1
 | 
			
		||||
    # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        split: [1, 2, 3, 4]
 | 
			
		||||
        include:
 | 
			
		||||
          - id: clang-format
 | 
			
		||||
            name: Run script/clang-format
 | 
			
		||||
          - id: clang-tidy
 | 
			
		||||
            name: Run script/clang-tidy 1/4
 | 
			
		||||
            split: 1
 | 
			
		||||
          - id: clang-tidy
 | 
			
		||||
            name: Run script/clang-tidy 2/4
 | 
			
		||||
            split: 2
 | 
			
		||||
          - id: clang-tidy
 | 
			
		||||
            name: Run script/clang-tidy 3/4
 | 
			
		||||
            split: 3
 | 
			
		||||
          - id: clang-tidy
 | 
			
		||||
            name: Run script/clang-tidy 4/4
 | 
			
		||||
            split: 4
 | 
			
		||||
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: ghcr.io/esphome/esphome-lint:1.1
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
@@ -45,26 +41,57 @@ jobs:
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
        run: pio init --ide atom
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-format
 | 
			
		||||
        run: script/clang-format -i
 | 
			
		||||
        if: ${{ matrix.id == 'clang-format' }}
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-tidy
 | 
			
		||||
        run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
 | 
			
		||||
        if: ${{ matrix.id == 'clang-tidy' }}
 | 
			
		||||
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-python:
 | 
			
		||||
  ci:
 | 
			
		||||
    # Don't use the esphome-lint docker image because it may contain outdated requirements.
 | 
			
		||||
    # This way, all dependencies are cached via the cache action.
 | 
			
		||||
    name: ${{ matrix.name }}
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        include:
 | 
			
		||||
          - id: ci-custom
 | 
			
		||||
            name: Run script/ci-custom
 | 
			
		||||
          - id: lint-python
 | 
			
		||||
            name: Run script/lint-python
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test1.yaml
 | 
			
		||||
            name: Test tests/test1.yaml
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test2.yaml
 | 
			
		||||
            name: Test tests/test2.yaml
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test3.yaml
 | 
			
		||||
            name: Test tests/test3.yaml
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test4.yaml
 | 
			
		||||
            name: Test tests/test4.yaml
 | 
			
		||||
          - id: pytest
 | 
			
		||||
            name: Run pytest
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
@@ -72,6 +99,17 @@ jobs:
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
 | 
			
		||||
      # Use per test platformio cache because tests have different platform versions
 | 
			
		||||
      - name: Cache ~/.platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            test-home-platformio-${{ matrix.file }}-
 | 
			
		||||
        if: ${{ matrix.id == 'test' }}
 | 
			
		||||
 | 
			
		||||
      - name: Set up python environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
 | 
			
		||||
@@ -80,82 +118,22 @@ jobs:
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/lint-python.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/pytest.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
 | 
			
		||||
      - name: Lint Custom
 | 
			
		||||
        run: script/ci-custom.py
 | 
			
		||||
        run: |
 | 
			
		||||
          script/ci-custom.py
 | 
			
		||||
          script/build_codeowners.py --check
 | 
			
		||||
        if: ${{ matrix.id == 'ci-custom' }}
 | 
			
		||||
      - name: Lint Python
 | 
			
		||||
        run: script/lint-python
 | 
			
		||||
      - name: Lint CODEOWNERS
 | 
			
		||||
        run: script/build_codeowners.py --check
 | 
			
		||||
        if: ${{ matrix.id == 'lint-python' }}
 | 
			
		||||
 | 
			
		||||
  test:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
          test:
 | 
			
		||||
          - test1
 | 
			
		||||
          - test2
 | 
			
		||||
          - test3
 | 
			
		||||
          - test4
 | 
			
		||||
          - test5
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      # Use per test platformio cache because tests have different platform versions
 | 
			
		||||
      - name: Cache ~/.platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            test-home-platformio-${{ matrix.test }}-
 | 
			
		||||
      - name: Set up environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
      - run: esphome compile ${{ matrix.file }}
 | 
			
		||||
        if: ${{ matrix.id == 'test' }}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - run: esphome compile tests/${{ matrix.test }}.yaml
 | 
			
		||||
 | 
			
		||||
  pytest:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      - name: Set up environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
      - name: Install Github Actions annotator
 | 
			
		||||
        run: pip install pytest-github-actions-annotate-failures
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - name: Run pytest
 | 
			
		||||
        run: |
 | 
			
		||||
          pytest \
 | 
			
		||||
            -qq \
 | 
			
		||||
            --durations=10 \
 | 
			
		||||
            -o console_output_style=count \
 | 
			
		||||
            tests
 | 
			
		||||
          pytest -vv --tb=native tests
 | 
			
		||||
        if: ${{ matrix.id == 'pytest' }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										108
									
								
								.github/workflows/docker-lint-build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										108
									
								
								.github/workflows/docker-lint-build.yml
									
									
									
									
										vendored
									
									
								
							@@ -13,30 +13,88 @@ on:
 | 
			
		||||
      - '.github/workflows/docker-lint-build.yml'
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  publish-docker-lint-iage:
 | 
			
		||||
    name: Build docker containers
 | 
			
		||||
  deploy-docker:
 | 
			
		||||
    name: Build and publish docker containers
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        build_type: ["lint"]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set TAG
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "TAG=1.1" >> $GITHUB_ENV
 | 
			
		||||
      - name: Pull for cache
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull "esphome/esphome-lint:latest" || true
 | 
			
		||||
      - name: Build
 | 
			
		||||
        run: |
 | 
			
		||||
          docker build \
 | 
			
		||||
            --cache-from "esphome/esphome-lint:latest" \
 | 
			
		||||
            --file "docker/Dockerfile.lint" \
 | 
			
		||||
            --tag "esphome/esphome-lint:latest" \
 | 
			
		||||
            --tag "esphome/esphome-lint:${TAG}" \
 | 
			
		||||
            .
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
        env:
 | 
			
		||||
          DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
        run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
			
		||||
      - run: |
 | 
			
		||||
          docker push "esphome/esphome-lint:${TAG}"
 | 
			
		||||
          docker push "esphome/esphome-lint:latest"
 | 
			
		||||
    - uses: actions/checkout@v2
 | 
			
		||||
    - name: Set up Python
 | 
			
		||||
      uses: actions/setup-python@v2
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: '3.9'
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        echo "TAG=1.1" >> $GITHUB_ENV
 | 
			
		||||
 | 
			
		||||
    - name: Run build
 | 
			
		||||
      run: |
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${TAG}" \
 | 
			
		||||
          --arch "${{ matrix.arch }}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          build
 | 
			
		||||
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
        username: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
        password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
    - name: Log in to the GitHub container registry
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
    - name: Run push
 | 
			
		||||
      run: |
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${TAG}" \
 | 
			
		||||
          --arch "${{ matrix.arch }}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          push
 | 
			
		||||
 | 
			
		||||
  deploy-docker-manifest:
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [deploy-docker]
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        build_type: ["lint"]
 | 
			
		||||
    steps:
 | 
			
		||||
    - uses: actions/checkout@v2
 | 
			
		||||
    - name: Set up Python
 | 
			
		||||
      uses: actions/setup-python@v2
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: '3.9'
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        echo "TAG=1.1" >> $GITHUB_ENV
 | 
			
		||||
    - name: Enable experimental manifest support
 | 
			
		||||
      run: |
 | 
			
		||||
        mkdir -p ~/.docker
 | 
			
		||||
        echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
 | 
			
		||||
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
        username: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
        password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
    - name: Log in to the GitHub container registry
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
    - name: Run manifest
 | 
			
		||||
      run: |
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${TAG}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          manifest
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								.github/workflows/matchers/pytest.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/matchers/pytest.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
{
 | 
			
		||||
  "problemMatcher": [
 | 
			
		||||
    {
 | 
			
		||||
      "owner": "pytest",
 | 
			
		||||
      "fileLocation": "absolute",
 | 
			
		||||
      "pattern": [
 | 
			
		||||
        {
 | 
			
		||||
          "regexp": "^\\s+File \"(.*)\", line (\\d+), in (.*)$",
 | 
			
		||||
          "file": 1,
 | 
			
		||||
          "line": 2
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "regexp": "^\\s+(.*)$",
 | 
			
		||||
          "message": 1
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										247
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										247
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,247 +0,0 @@
 | 
			
		||||
name: Publish dev releases to docker hub
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
    - dev
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
 | 
			
		||||
 | 
			
		||||
  lint-clang-format:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: esphome/esphome-lint:1.1
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
        run: pio init --ide atom
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-format
 | 
			
		||||
        run: script/clang-format -i
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-clang-tidy:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: esphome/esphome-lint:1.1
 | 
			
		||||
    # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        split: [1, 2, 3, 4]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
        run: pio init --ide atom
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
      - name: Run clang-tidy
 | 
			
		||||
        run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-python:
 | 
			
		||||
    # Don't use the esphome-lint docker image because it may contain outdated requirements.
 | 
			
		||||
    # This way, all dependencies are cached via the cache action.
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      - name: Set up python environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/lint-python.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - name: Lint Custom
 | 
			
		||||
        run: script/ci-custom.py
 | 
			
		||||
      - name: Lint Python
 | 
			
		||||
        run: script/lint-python
 | 
			
		||||
      - name: Lint CODEOWNERS
 | 
			
		||||
        run: script/build_codeowners.py --check
 | 
			
		||||
 | 
			
		||||
  test:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
          test:
 | 
			
		||||
          - test1
 | 
			
		||||
          - test2
 | 
			
		||||
          - test3
 | 
			
		||||
          - test4
 | 
			
		||||
          - test5
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      # Use per test platformio cache because tests have different platform versions
 | 
			
		||||
      - name: Cache ~/.platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            test-home-platformio-${{ matrix.test }}-
 | 
			
		||||
      - name: Set up environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - run: esphome compile tests/${{ matrix.test }}.yaml
 | 
			
		||||
 | 
			
		||||
  pytest:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      - name: Set up environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
      - name: Install Github Actions annotator
 | 
			
		||||
        run: pip install pytest-github-actions-annotate-failures
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - name: Run pytest
 | 
			
		||||
        run: |
 | 
			
		||||
          pytest \
 | 
			
		||||
            -qq \
 | 
			
		||||
            --durations=10 \
 | 
			
		||||
            -o console_output_style=count \
 | 
			
		||||
            tests
 | 
			
		||||
 | 
			
		||||
  deploy-docker:
 | 
			
		||||
    name: Build and publish docker containers
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        # Hassio dev image doesn't use esphome/esphome-hassio-$arch and uses base directly
 | 
			
		||||
        build_type: ["docker"]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set TAG
 | 
			
		||||
        run: |
 | 
			
		||||
          TAG="${GITHUB_SHA:0:7}"
 | 
			
		||||
          echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Set up env variables
 | 
			
		||||
        run: |
 | 
			
		||||
          base_version="3.4.0"
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
			
		||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
            build_to="esphome/esphome-hassio-${{ matrix.arch }}"
 | 
			
		||||
            dockerfile="docker/Dockerfile.hassio"
 | 
			
		||||
          else
 | 
			
		||||
            build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
            build_to="esphome/esphome-${{ matrix.arch }}"
 | 
			
		||||
            dockerfile="docker/Dockerfile"
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
          echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
 | 
			
		||||
          echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
 | 
			
		||||
          echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Pull for cache
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull "${BUILD_TO}:dev" || true
 | 
			
		||||
      - name: Register QEMU binfmt
 | 
			
		||||
        run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
 | 
			
		||||
      - run: |
 | 
			
		||||
          docker build \
 | 
			
		||||
            --build-arg "BUILD_FROM=${BUILD_FROM}" \
 | 
			
		||||
            --build-arg "BUILD_VERSION=${TAG}" \
 | 
			
		||||
            --tag "${BUILD_TO}:${TAG}" \
 | 
			
		||||
            --tag "${BUILD_TO}:dev" \
 | 
			
		||||
            --cache-from "${BUILD_TO}:dev" \
 | 
			
		||||
            --file "${DOCKERFILE}" \
 | 
			
		||||
            .
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
        env:
 | 
			
		||||
          DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
        run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
			
		||||
      - run: |
 | 
			
		||||
          docker push "${BUILD_TO}:${TAG}"
 | 
			
		||||
          docker push "${BUILD_TO}:dev"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  deploy-docker-manifest:
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [deploy-docker]
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Enable experimental manifest support
 | 
			
		||||
      run: |
 | 
			
		||||
        mkdir -p ~/.docker
 | 
			
		||||
        echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        TAG="${GITHUB_SHA:0:7}"
 | 
			
		||||
        echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
        DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
      run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
			
		||||
    - name: "Create the manifest"
 | 
			
		||||
      run: |
 | 
			
		||||
        docker manifest create esphome/esphome:${TAG} \
 | 
			
		||||
          esphome/esphome-aarch64:${TAG} \
 | 
			
		||||
          esphome/esphome-amd64:${TAG} \
 | 
			
		||||
          esphome/esphome-armv7:${TAG}
 | 
			
		||||
        docker manifest push esphome/esphome:${TAG}
 | 
			
		||||
 | 
			
		||||
        docker manifest create esphome/esphome:dev \
 | 
			
		||||
          esphome/esphome-aarch64:${TAG} \
 | 
			
		||||
          esphome/esphome-amd64:${TAG} \
 | 
			
		||||
          esphome/esphome-armv7:${TAG}
 | 
			
		||||
        docker manifest push esphome/esphome:dev
 | 
			
		||||
							
								
								
									
										317
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										317
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,164 +1,35 @@
 | 
			
		||||
name: Publish Release
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
  release:
 | 
			
		||||
    types: [published]
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: "0 2 * * *"
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
 | 
			
		||||
 | 
			
		||||
  lint-clang-format:
 | 
			
		||||
  init:
 | 
			
		||||
    name: Initialize build
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: esphome/esphome-lint:1.1
 | 
			
		||||
    outputs:
 | 
			
		||||
      tag: ${{ steps.tag.outputs.tag }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
        run: pio init --ide atom
 | 
			
		||||
 | 
			
		||||
      - name: Run clang-format
 | 
			
		||||
        run: script/clang-format -i
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-clang-tidy:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
    # doesn't have to be installed
 | 
			
		||||
    container: esphome/esphome-lint:1.1
 | 
			
		||||
    # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        split: [1, 2, 3, 4]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
        run: pio init --ide atom
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
      - name: Get tag
 | 
			
		||||
        id: tag
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
      - name: Run clang-tidy
 | 
			
		||||
        run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-python:
 | 
			
		||||
    # Don't use the esphome-lint docker image because it may contain outdated requirements.
 | 
			
		||||
    # This way, all dependencies are cached via the cache action.
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      - name: Set up python environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/lint-python.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - name: Lint Custom
 | 
			
		||||
        run: script/ci-custom.py
 | 
			
		||||
      - name: Lint Python
 | 
			
		||||
        run: script/lint-python
 | 
			
		||||
      - name: Lint CODEOWNERS
 | 
			
		||||
        run: script/build_codeowners.py --check
 | 
			
		||||
 | 
			
		||||
  test:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
          test:
 | 
			
		||||
          - test1
 | 
			
		||||
          - test2
 | 
			
		||||
          - test3
 | 
			
		||||
          - test4
 | 
			
		||||
          - test5
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      # Use per test platformio cache because tests have different platform versions
 | 
			
		||||
      - name: Cache ~/.platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            test-home-platformio-${{ matrix.test }}-
 | 
			
		||||
      - name: Set up environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - run: esphome compile tests/${{ matrix.test }}.yaml
 | 
			
		||||
 | 
			
		||||
  pytest:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - name: Cache pip modules
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.cache/pip
 | 
			
		||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            esphome-pip-3.7-
 | 
			
		||||
      - name: Set up environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
      - name: Install Github Actions annotator
 | 
			
		||||
        run: pip install pytest-github-actions-annotate-failures
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - name: Run pytest
 | 
			
		||||
        run: |
 | 
			
		||||
          pytest \
 | 
			
		||||
            -qq \
 | 
			
		||||
            --durations=10 \
 | 
			
		||||
            -o console_output_style=count \
 | 
			
		||||
            tests
 | 
			
		||||
          if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then
 | 
			
		||||
            TAG="${GITHUB_REF#refs/tags/v}"
 | 
			
		||||
          else
 | 
			
		||||
            TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
 | 
			
		||||
            today="$(date --utc '+%Y%m%d')"
 | 
			
		||||
            TAG="${TAG}${today}"
 | 
			
		||||
          fi
 | 
			
		||||
          echo "::set-output name=tag::${TAG}"
 | 
			
		||||
 | 
			
		||||
  deploy-pypi:
 | 
			
		||||
    name: Build and publish to PyPi
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
 | 
			
		||||
    if: github.repository == 'esphome/esphome' && github.event_name == 'release'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
@@ -182,119 +53,85 @@ jobs:
 | 
			
		||||
    name: Build and publish docker containers
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
 | 
			
		||||
    needs: [init]
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        build_type: ["hassio", "docker"]
 | 
			
		||||
        build_type: ["ha-addon", "docker"]
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set TAG
 | 
			
		||||
        run: |
 | 
			
		||||
          TAG="${GITHUB_REF#refs/tags/v}"
 | 
			
		||||
          echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Set up env variables
 | 
			
		||||
        run: |
 | 
			
		||||
          base_version="3.4.0"
 | 
			
		||||
    - uses: actions/checkout@v2
 | 
			
		||||
    - name: Set up Python
 | 
			
		||||
      uses: actions/setup-python@v2
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: '3.9'
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
			
		||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
            build_to="esphome/esphome-hassio-${{ matrix.arch }}"
 | 
			
		||||
            dockerfile="docker/Dockerfile.hassio"
 | 
			
		||||
          else
 | 
			
		||||
            build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
            build_to="esphome/esphome-${{ matrix.arch }}"
 | 
			
		||||
            dockerfile="docker/Dockerfile"
 | 
			
		||||
          fi
 | 
			
		||||
    - name: Run build
 | 
			
		||||
      run: |
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${{ needs.init.outputs.tag }}" \
 | 
			
		||||
          --arch "${{ matrix.arch }}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          build
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
 | 
			
		||||
            cache_tag="beta"
 | 
			
		||||
          else
 | 
			
		||||
            cache_tag="latest"
 | 
			
		||||
          fi
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
        username: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
        password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
    - name: Log in to the GitHub container registry
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
          # Set env variables so these values don't need to be calculated again
 | 
			
		||||
          echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
 | 
			
		||||
          echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
 | 
			
		||||
          echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
 | 
			
		||||
          echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Pull for cache
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull "${BUILD_TO}:${CACHE_TAG}" || true
 | 
			
		||||
      - name: Register QEMU binfmt
 | 
			
		||||
        run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
 | 
			
		||||
      - run: |
 | 
			
		||||
          docker build \
 | 
			
		||||
            --build-arg "BUILD_FROM=${BUILD_FROM}" \
 | 
			
		||||
            --build-arg "BUILD_VERSION=${TAG}" \
 | 
			
		||||
            --tag "${BUILD_TO}:${TAG}" \
 | 
			
		||||
            --cache-from "${BUILD_TO}:${CACHE_TAG}" \
 | 
			
		||||
            --file "${DOCKERFILE}" \
 | 
			
		||||
            .
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
        env:
 | 
			
		||||
          DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
        run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
			
		||||
      - run: docker push "${BUILD_TO}:${TAG}"
 | 
			
		||||
 | 
			
		||||
      # Always publish to beta tag (also full releases)
 | 
			
		||||
      - name: Publish docker beta tag
 | 
			
		||||
        run: |
 | 
			
		||||
          docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta"
 | 
			
		||||
          docker push "${BUILD_TO}:beta"
 | 
			
		||||
 | 
			
		||||
      - if: ${{ !github.event.release.prerelease }}
 | 
			
		||||
        name: Publish docker latest tag
 | 
			
		||||
        run: |
 | 
			
		||||
          docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest"
 | 
			
		||||
          docker push "${BUILD_TO}:latest"
 | 
			
		||||
    - name: Run push
 | 
			
		||||
      run: |
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${{ needs.init.outputs.tag }}" \
 | 
			
		||||
          --arch "${{ matrix.arch }}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          push
 | 
			
		||||
 | 
			
		||||
  deploy-docker-manifest:
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [deploy-docker]
 | 
			
		||||
    needs: [init, deploy-docker]
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        build_type: ["ha-addon", "docker"]
 | 
			
		||||
    steps:
 | 
			
		||||
    - uses: actions/checkout@v2
 | 
			
		||||
    - name: Set up Python
 | 
			
		||||
      uses: actions/setup-python@v2
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: '3.9'
 | 
			
		||||
    - name: Enable experimental manifest support
 | 
			
		||||
      run: |
 | 
			
		||||
        mkdir -p ~/.docker
 | 
			
		||||
        echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        TAG="${GITHUB_REF#refs/tags/v}"
 | 
			
		||||
        echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
        DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
      run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
			
		||||
    - name: "Create the manifest"
 | 
			
		||||
      run: |
 | 
			
		||||
        docker manifest create esphome/esphome:${TAG} \
 | 
			
		||||
          esphome/esphome-aarch64:${TAG} \
 | 
			
		||||
          esphome/esphome-amd64:${TAG} \
 | 
			
		||||
          esphome/esphome-armv7:${TAG}
 | 
			
		||||
        docker manifest push esphome/esphome:${TAG}
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
        username: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
        password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
    - name: Log in to the GitHub container registry
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
      with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
    - name: Publish docker beta tag
 | 
			
		||||
    - name: Run manifest
 | 
			
		||||
      run: |
 | 
			
		||||
        docker manifest create esphome/esphome:beta \
 | 
			
		||||
          esphome/esphome-aarch64:${TAG} \
 | 
			
		||||
          esphome/esphome-amd64:${TAG} \
 | 
			
		||||
          esphome/esphome-armv7:${TAG}
 | 
			
		||||
        docker manifest push esphome/esphome:beta
 | 
			
		||||
 | 
			
		||||
    - name: Publish docker latest tag
 | 
			
		||||
      if: ${{ !github.event.release.prerelease }}
 | 
			
		||||
      run: |
 | 
			
		||||
        docker manifest create esphome/esphome:latest \
 | 
			
		||||
          esphome/esphome-aarch64:${TAG} \
 | 
			
		||||
          esphome/esphome-amd64:${TAG} \
 | 
			
		||||
          esphome/esphome-armv7:${TAG}
 | 
			
		||||
        docker manifest push esphome/esphome:latest
 | 
			
		||||
        docker/build.py \
 | 
			
		||||
          --tag "${{ needs.init.outputs.tag }}" \
 | 
			
		||||
          --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
          manifest
 | 
			
		||||
 | 
			
		||||
  deploy-hassio-repo:
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    if: github.repository == 'esphome/esphome' && github.event_name == 'release'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [deploy-docker]
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -307,4 +144,4 @@ jobs:
 | 
			
		||||
            -X POST \
 | 
			
		||||
            -H "Accept: application/vnd.github.v3+json" \
 | 
			
		||||
            https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \
 | 
			
		||||
            -d "{\"ref\":\"master\",\"inputs\":{\"version\":\"$TAG\"}}"
 | 
			
		||||
            -d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -13,6 +13,9 @@ __pycache__/
 | 
			
		||||
# Intellij Idea
 | 
			
		||||
.idea
 | 
			
		||||
 | 
			
		||||
# Vim
 | 
			
		||||
*.swp
 | 
			
		||||
 | 
			
		||||
# Hide some OS X stuff
 | 
			
		||||
.DS_Store
 | 
			
		||||
.AppleDouble
 | 
			
		||||
 
 | 
			
		||||
@@ -23,5 +23,5 @@ repos:
 | 
			
		||||
      - id: no-commit-to-branch
 | 
			
		||||
        args:
 | 
			
		||||
          - --branch=dev
 | 
			
		||||
          - --branch=master
 | 
			
		||||
          - --branch=release
 | 
			
		||||
          - --branch=beta
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							@@ -1,11 +1,32 @@
 | 
			
		||||
{
 | 
			
		||||
    "version": "2.0.0",
 | 
			
		||||
    "tasks": [
 | 
			
		||||
  "version": "2.0.0",
 | 
			
		||||
  "tasks": [
 | 
			
		||||
    {
 | 
			
		||||
      "label": "run",
 | 
			
		||||
      "type": "shell",
 | 
			
		||||
      "command": "python3 -m esphome dashboard config/",
 | 
			
		||||
      "problemMatcher": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "label": "clang-tidy",
 | 
			
		||||
      "type": "shell",
 | 
			
		||||
      "command": "test -f .gcc-flags.json || pio init --silent --ide atom; ./script/clang-tidy",
 | 
			
		||||
      "problemMatcher": [
 | 
			
		||||
        {
 | 
			
		||||
            "label": "run",
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "python3 -m esphome dashboard config",
 | 
			
		||||
            "problemMatcher": []
 | 
			
		||||
          "owner": "clang-tidy",
 | 
			
		||||
          "fileLocation": "absolute",
 | 
			
		||||
          "pattern": [
 | 
			
		||||
            {
 | 
			
		||||
              "regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*) \\[([a-z0-9,\\-]+)\\]\\s*$",
 | 
			
		||||
              "file": 1,
 | 
			
		||||
              "line": 2,
 | 
			
		||||
              "column": 3,
 | 
			
		||||
              "severity": 4,
 | 
			
		||||
              "message": 5
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,10 +15,12 @@ esphome/components/ac_dimmer/* @glmnet
 | 
			
		||||
esphome/components/adc/* @esphome/core
 | 
			
		||||
esphome/components/addressable_light/* @justfalter
 | 
			
		||||
esphome/components/animation/* @syndlex
 | 
			
		||||
esphome/components/anova/* @buxtronix
 | 
			
		||||
esphome/components/api/* @OttoWinter
 | 
			
		||||
esphome/components/async_tcp/* @OttoWinter
 | 
			
		||||
esphome/components/atc_mithermometer/* @ahpohl
 | 
			
		||||
esphome/components/b_parasite/* @rbaron
 | 
			
		||||
esphome/components/ballu/* @bazuchan
 | 
			
		||||
esphome/components/bang_bang/* @OttoWinter
 | 
			
		||||
esphome/components/binary_sensor/* @esphome/core
 | 
			
		||||
esphome/components/ble_client/* @buxtronix
 | 
			
		||||
@@ -45,6 +47,7 @@ esphome/components/fingerprint_grow/* @OnFreund @loongyh
 | 
			
		||||
esphome/components/globals/* @esphome/core
 | 
			
		||||
esphome/components/gpio/* @esphome/core
 | 
			
		||||
esphome/components/gps/* @coogle
 | 
			
		||||
esphome/components/havells_solar/* @sourabhjaiswal
 | 
			
		||||
esphome/components/homeassistant/* @OttoWinter
 | 
			
		||||
esphome/components/i2c/* @esphome/core
 | 
			
		||||
esphome/components/improv/* @jesserockz
 | 
			
		||||
@@ -70,7 +73,13 @@ esphome/components/midea_ac/* @dudanov
 | 
			
		||||
esphome/components/midea_dongle/* @dudanov
 | 
			
		||||
esphome/components/mitsubishi/* @RubyBailey
 | 
			
		||||
esphome/components/network/* @esphome/core
 | 
			
		||||
esphome/components/nextion/* @senexcrenshaw
 | 
			
		||||
esphome/components/nextion/binary_sensor/* @senexcrenshaw
 | 
			
		||||
esphome/components/nextion/sensor/* @senexcrenshaw
 | 
			
		||||
esphome/components/nextion/switch/* @senexcrenshaw
 | 
			
		||||
esphome/components/nextion/text_sensor/* @senexcrenshaw
 | 
			
		||||
esphome/components/nfc/* @jesserockz
 | 
			
		||||
esphome/components/number/* @esphome/core
 | 
			
		||||
esphome/components/ota/* @esphome/core
 | 
			
		||||
esphome/components/output/* @esphome/core
 | 
			
		||||
esphome/components/pid/* @OttoWinter
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# ESPHome [](https://travis-ci.org/esphome/esphome) [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/)
 | 
			
		||||
# ESPHome [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/)
 | 
			
		||||
 | 
			
		||||
[](https://esphome.io/)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
ARG BUILD_FROM=esphome/esphome-base-amd64:3.4.0
 | 
			
		||||
ARG BUILD_FROM=esphome/esphome-base:latest
 | 
			
		||||
FROM ${BUILD_FROM}
 | 
			
		||||
 | 
			
		||||
# First install requirements to leverage caching when requirements don't change
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1 @@
 | 
			
		||||
FROM esphome/esphome-base-amd64:3.4.0
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
RUN apt-get update \
 | 
			
		||||
    && apt-get install -y --no-install-recommends \
 | 
			
		||||
        python3-wheel \
 | 
			
		||||
        net-tools \
 | 
			
		||||
    && apt-get clean \
 | 
			
		||||
    && rm -rf /var/lib/apt/lists/*
 | 
			
		||||
 | 
			
		||||
WORKDIR /workspaces
 | 
			
		||||
ENV SHELL /bin/bash
 | 
			
		||||
FROM esphome/esphome-lint:1.1
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
ARG BUILD_FROM
 | 
			
		||||
ARG BUILD_FROM=esphome/esphome-hassio-base:latest
 | 
			
		||||
FROM ${BUILD_FROM}
 | 
			
		||||
 | 
			
		||||
# First install requirements to leverage caching when requirements don't change
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
FROM esphome/esphome-lint-base:3.4.0
 | 
			
		||||
ARG BUILD_FROM=esphome/esphome-lint-base:latest
 | 
			
		||||
FROM ${BUILD_FROM}
 | 
			
		||||
 | 
			
		||||
COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py  platformio.ini /
 | 
			
		||||
RUN \
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										177
									
								
								docker/build.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										177
									
								
								docker/build.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,177 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
import subprocess
 | 
			
		||||
import argparse
 | 
			
		||||
import platform
 | 
			
		||||
import shlex
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CHANNEL_DEV = 'dev'
 | 
			
		||||
CHANNEL_BETA = 'beta'
 | 
			
		||||
CHANNEL_RELEASE = 'release'
 | 
			
		||||
CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
 | 
			
		||||
 | 
			
		||||
ARCH_AMD64 = 'amd64'
 | 
			
		||||
ARCH_ARMV7 = 'armv7'
 | 
			
		||||
ARCH_AARCH64 = 'aarch64'
 | 
			
		||||
ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64]
 | 
			
		||||
 | 
			
		||||
TYPE_DOCKER = 'docker'
 | 
			
		||||
TYPE_HA_ADDON = 'ha-addon'
 | 
			
		||||
TYPE_LINT = 'lint'
 | 
			
		||||
TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BASE_VERSION = "3.6.0"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
parser = argparse.ArgumentParser()
 | 
			
		||||
parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag")
 | 
			
		||||
parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for")
 | 
			
		||||
parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run")
 | 
			
		||||
parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them")
 | 
			
		||||
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
 | 
			
		||||
build_parser = subparsers.add_parser("build", help="Build the image")
 | 
			
		||||
push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub")
 | 
			
		||||
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# only lists some possibilities, doesn't have to be perfect
 | 
			
		||||
# https://stackoverflow.com/a/45125525
 | 
			
		||||
UNAME_TO_ARCH = {
 | 
			
		||||
    "x86_64": ARCH_AMD64,
 | 
			
		||||
    "aarch64": ARCH_AARCH64,
 | 
			
		||||
    "aarch64_be": ARCH_AARCH64,
 | 
			
		||||
    "arm": ARCH_ARMV7,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dataclass(frozen=True)
 | 
			
		||||
class DockerParams:
 | 
			
		||||
    build_from: str
 | 
			
		||||
    build_to: str
 | 
			
		||||
    manifest_to: str
 | 
			
		||||
    dockerfile: str
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def for_type_arch(cls, build_type, arch):
 | 
			
		||||
        prefix = {
 | 
			
		||||
            TYPE_DOCKER: "esphome/esphome",
 | 
			
		||||
            TYPE_HA_ADDON: "esphome/esphome-hassio",
 | 
			
		||||
            TYPE_LINT: "esphome/esphome-lint"
 | 
			
		||||
        }[build_type]
 | 
			
		||||
        build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}"
 | 
			
		||||
        build_to = f"{prefix}-{arch}"
 | 
			
		||||
        dockerfile = {
 | 
			
		||||
            TYPE_DOCKER: "docker/Dockerfile",
 | 
			
		||||
            TYPE_HA_ADDON: "docker/Dockerfile.hassio",
 | 
			
		||||
            TYPE_LINT: "docker/Dockerfile.lint",
 | 
			
		||||
        }[build_type]
 | 
			
		||||
        return cls(
 | 
			
		||||
            build_from=build_from,
 | 
			
		||||
            build_to=build_to,
 | 
			
		||||
            manifest_to=prefix,
 | 
			
		||||
            dockerfile=dockerfile
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    def run_command(*cmd, ignore_error: bool = False):
 | 
			
		||||
        print(f"$ {shlex.join(list(cmd))}")
 | 
			
		||||
        if not args.dry_run:
 | 
			
		||||
            rc = subprocess.call(list(cmd))
 | 
			
		||||
            if rc != 0 and not ignore_error:
 | 
			
		||||
                print("Command failed")
 | 
			
		||||
                sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    # detect channel from tag
 | 
			
		||||
    match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag)
 | 
			
		||||
    if match is None:
 | 
			
		||||
        channel = CHANNEL_DEV
 | 
			
		||||
    elif match.group(1) is None:
 | 
			
		||||
        channel = CHANNEL_RELEASE
 | 
			
		||||
    else:
 | 
			
		||||
        channel = CHANNEL_BETA
 | 
			
		||||
 | 
			
		||||
    tags_to_push = [args.tag]
 | 
			
		||||
    if channel == CHANNEL_DEV:
 | 
			
		||||
        tags_to_push.append("dev")
 | 
			
		||||
    elif channel == CHANNEL_BETA:
 | 
			
		||||
        tags_to_push.append("beta")
 | 
			
		||||
    elif channel == CHANNEL_RELEASE:
 | 
			
		||||
        # Additionally push to beta
 | 
			
		||||
        tags_to_push.append("beta")
 | 
			
		||||
        tags_to_push.append("latest")
 | 
			
		||||
 | 
			
		||||
    if args.command == "build":
 | 
			
		||||
        # 1. pull cache image
 | 
			
		||||
        params = DockerParams.for_type_arch(args.build_type, args.arch)
 | 
			
		||||
        cache_tag = {
 | 
			
		||||
            CHANNEL_DEV: "dev",
 | 
			
		||||
            CHANNEL_BETA: "beta",
 | 
			
		||||
            CHANNEL_RELEASE: "latest",
 | 
			
		||||
        }[channel]
 | 
			
		||||
        cache_img = f"ghcr.io/{params.build_to}:{cache_tag}"
 | 
			
		||||
        run_command("docker", "pull", cache_img, ignore_error=True)
 | 
			
		||||
 | 
			
		||||
        # 2. register QEMU binfmt (if not host arch)
 | 
			
		||||
        is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch
 | 
			
		||||
        if not is_native:
 | 
			
		||||
            run_command(
 | 
			
		||||
                "docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2",
 | 
			
		||||
                "--reset", "-p", "yes"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        # 3. build
 | 
			
		||||
        run_command(
 | 
			
		||||
            "docker", "build",
 | 
			
		||||
            "--build-arg", f"BUILD_FROM={params.build_from}",
 | 
			
		||||
            "--build-arg", f"BUILD_VERSION={args.tag}",
 | 
			
		||||
            "--tag", f"{params.build_to}:{args.tag}",
 | 
			
		||||
            "--cache-from", cache_img,
 | 
			
		||||
            "--file", params.dockerfile,
 | 
			
		||||
            "."
 | 
			
		||||
        )
 | 
			
		||||
    elif args.command == "push":
 | 
			
		||||
        params = DockerParams.for_type_arch(args.build_type, args.arch)
 | 
			
		||||
        imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push]
 | 
			
		||||
        imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push]
 | 
			
		||||
        src = imgs[0]
 | 
			
		||||
        # 1. tag images
 | 
			
		||||
        for img in imgs[1:]:
 | 
			
		||||
            run_command(
 | 
			
		||||
                "docker", "tag", src, img
 | 
			
		||||
            )
 | 
			
		||||
        # 2. push images
 | 
			
		||||
        for img in imgs:
 | 
			
		||||
            run_command(
 | 
			
		||||
                "docker", "push", img
 | 
			
		||||
            )
 | 
			
		||||
    elif args.command == "manifest":
 | 
			
		||||
        manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to
 | 
			
		||||
 | 
			
		||||
        targets = [f"{manifest}:{tag}" for tag in tags_to_push]
 | 
			
		||||
        targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push]
 | 
			
		||||
        # 1. Create manifests
 | 
			
		||||
        for target in targets:
 | 
			
		||||
            cmd = ["docker", "manifest", "create", target]
 | 
			
		||||
            for arch in ARCHS:
 | 
			
		||||
                src = f"{DockerParams.for_type_arch(args.build_type, arch).build_to}:{args.tag}"
 | 
			
		||||
                if target.startswith("ghcr.io"):
 | 
			
		||||
                    src = f"ghcr.io/{src}"
 | 
			
		||||
                cmd.append(src)
 | 
			
		||||
            run_command(*cmd)
 | 
			
		||||
        # 2. Push manifests
 | 
			
		||||
        for target in targets:
 | 
			
		||||
            run_command(
 | 
			
		||||
                "docker", "manifest", "push", target
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
@@ -514,14 +514,26 @@ def parse_args(argv):
 | 
			
		||||
 | 
			
		||||
    compat_parser.error = _raise
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        result, unparsed = compat_parser.parse_known_args(argv[1:])
 | 
			
		||||
        last_option = len(argv) - len(unparsed) - 1 - len(result.configuration)
 | 
			
		||||
        argv = argv[0:last_option] + [result.command] + result.configuration + unparsed
 | 
			
		||||
        deprecated_argv_suggestion = argv
 | 
			
		||||
    except argparse.ArgumentError:
 | 
			
		||||
        # This is not an old-style command line, so we don't have to do anything.
 | 
			
		||||
        deprecated_argv_suggestion = None
 | 
			
		||||
    deprecated_argv_suggestion = None
 | 
			
		||||
 | 
			
		||||
    if ["dashboard", "config"] == argv[1:3] or ["version"] == argv[1:2]:
 | 
			
		||||
        # this is most likely meant in new-style arg format. do not try compat parsing
 | 
			
		||||
        pass
 | 
			
		||||
    else:
 | 
			
		||||
        try:
 | 
			
		||||
            result, unparsed = compat_parser.parse_known_args(argv[1:])
 | 
			
		||||
            last_option = len(argv) - len(unparsed) - 1 - len(result.configuration)
 | 
			
		||||
            unparsed = [
 | 
			
		||||
                "--device" if arg in ("--upload-port", "--serial-port") else arg
 | 
			
		||||
                for arg in unparsed
 | 
			
		||||
            ]
 | 
			
		||||
            argv = (
 | 
			
		||||
                argv[0:last_option] + [result.command] + result.configuration + unparsed
 | 
			
		||||
            )
 | 
			
		||||
            deprecated_argv_suggestion = argv
 | 
			
		||||
        except argparse.ArgumentError:
 | 
			
		||||
            # This is not an old-style command line, so we don't have to do anything.
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    # And continue on with regular parsing
 | 
			
		||||
    parser = argparse.ArgumentParser(
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,7 @@ from esphome.cpp_types import (  # noqa
 | 
			
		||||
    uint8,
 | 
			
		||||
    uint16,
 | 
			
		||||
    uint32,
 | 
			
		||||
    uint64,
 | 
			
		||||
    int32,
 | 
			
		||||
    const_char_ptr,
 | 
			
		||||
    NAN,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace a4988 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "a4988.stepper";
 | 
			
		||||
static const char *const TAG = "a4988.stepper";
 | 
			
		||||
 | 
			
		||||
void A4988::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up A4988...");
 | 
			
		||||
 
 | 
			
		||||
@@ -9,15 +9,15 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ac_dimmer {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "ac_dimmer";
 | 
			
		||||
static const char *const TAG = "ac_dimmer";
 | 
			
		||||
 | 
			
		||||
// Global array to store dimmer objects
 | 
			
		||||
static AcDimmerDataStore *all_dimmers[32];
 | 
			
		||||
static AcDimmerDataStore *all_dimmers[32];  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
/// Time in microseconds the gate should be held high
 | 
			
		||||
/// 10µs should be long enough for most triacs
 | 
			
		||||
/// For reference: BT136 datasheet says 2µs nominal (page 7)
 | 
			
		||||
static uint32_t GATE_ENABLE_TIME = 10;
 | 
			
		||||
static const uint32_t GATE_ENABLE_TIME = 10;
 | 
			
		||||
 | 
			
		||||
/// Function called from timer interrupt
 | 
			
		||||
/// Input is current time in microseconds (micros())
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace adalight {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "adalight_light_effect";
 | 
			
		||||
static const char *const TAG = "adalight_light_effect";
 | 
			
		||||
 | 
			
		||||
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
 | 
			
		||||
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ ADC_MODE(ADC_VCC)
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace adc {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "adc";
 | 
			
		||||
static const char *const TAG = "adc";
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace addressable_light {
 | 
			
		||||
 | 
			
		||||
static const char* TAG = "addressable_light.display";
 | 
			
		||||
static const char *const TAG = "addressable_light.display";
 | 
			
		||||
 | 
			
		||||
int AddressableLightDisplay::get_width_internal() { return this->width_; }
 | 
			
		||||
int AddressableLightDisplay::get_height_internal() { return this->height_; }
 | 
			
		||||
@@ -24,7 +24,7 @@ void AddressableLightDisplay::update() {
 | 
			
		||||
void AddressableLightDisplay::display() {
 | 
			
		||||
  bool dirty = false;
 | 
			
		||||
  uint8_t old_r, old_g, old_b, old_w;
 | 
			
		||||
  Color* c;
 | 
			
		||||
  Color *c;
 | 
			
		||||
 | 
			
		||||
  for (uint32_t offset = 0; offset < this->addressable_light_buffer_.size(); offset++) {
 | 
			
		||||
    c = &(this->addressable_light_buffer_[offset]);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ade7953 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "ade7953";
 | 
			
		||||
static const char *const TAG = "ade7953";
 | 
			
		||||
 | 
			
		||||
void ADE7953::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ADE7953:");
 | 
			
		||||
@@ -21,8 +21,8 @@ void ADE7953::dump_config() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define ADE_PUBLISH_(name, factor) \
 | 
			
		||||
  if (name && this->name##_sensor_) { \
 | 
			
		||||
    float value = *name / factor; \
 | 
			
		||||
  if ((name) && this->name##_sensor_) { \
 | 
			
		||||
    float value = *(name) / (factor); \
 | 
			
		||||
    this->name##_sensor_->publish_state(value); \
 | 
			
		||||
  }
 | 
			
		||||
#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor)
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ads1115 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "ads1115";
 | 
			
		||||
static const char *const TAG = "ads1115";
 | 
			
		||||
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
 | 
			
		||||
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace aht10 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "aht10";
 | 
			
		||||
static const char *const TAG = "aht10";
 | 
			
		||||
static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1};
 | 
			
		||||
static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
 | 
			
		||||
static const uint8_t AHT10_DEFAULT_DELAY = 5;    // ms, for calibration and temperature measurement
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace am2320 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "am2320";
 | 
			
		||||
static const char *const TAG = "am2320";
 | 
			
		||||
 | 
			
		||||
// ---=== Calc CRC16 ===---
 | 
			
		||||
uint16_t crc_16(uint8_t *ptr, uint8_t length) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										140
									
								
								esphome/components/anova/anova.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								esphome/components/anova/anova.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
#include "anova.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace anova {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "anova";
 | 
			
		||||
 | 
			
		||||
using namespace esphome::climate;
 | 
			
		||||
 | 
			
		||||
void Anova::dump_config() { LOG_CLIMATE("", "Anova BLE Cooker", this); }
 | 
			
		||||
 | 
			
		||||
void Anova::setup() {
 | 
			
		||||
  this->codec_ = new AnovaCodec();
 | 
			
		||||
  this->current_request_ = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Anova::loop() {}
 | 
			
		||||
 | 
			
		||||
void Anova::control(const ClimateCall &call) {
 | 
			
		||||
  if (call.get_mode().has_value()) {
 | 
			
		||||
    ClimateMode mode = *call.get_mode();
 | 
			
		||||
    AnovaPacket *pkt;
 | 
			
		||||
    switch (mode) {
 | 
			
		||||
      case climate::CLIMATE_MODE_OFF:
 | 
			
		||||
        pkt = this->codec_->get_stop_request();
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_MODE_HEAT:
 | 
			
		||||
        pkt = this->codec_->get_start_request();
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        ESP_LOGW(TAG, "Unsupported mode: %d", mode);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
 | 
			
		||||
                                           pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status)
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
  }
 | 
			
		||||
  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_->gattc_if, this->parent_->conn_id, this->char_handle_,
 | 
			
		||||
                                           pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status)
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
      this->current_temperature = NAN;
 | 
			
		||||
      this->target_temperature = NAN;
 | 
			
		||||
      this->publish_state();
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
      auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
 | 
			
		||||
      if (chr == nullptr) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str());
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      this->char_handle_ = chr->handle;
 | 
			
		||||
 | 
			
		||||
      auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
 | 
			
		||||
      if (status) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      this->node_state = espbt::ClientState::Established;
 | 
			
		||||
      this->current_request_ = 0;
 | 
			
		||||
      this->update();
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_NOTIFY_EVT: {
 | 
			
		||||
      if (param->notify.handle != this->char_handle_)
 | 
			
		||||
        break;
 | 
			
		||||
      this->codec_->decode(param->notify.value, param->notify.value_len);
 | 
			
		||||
      if (this->codec_->has_target_temp()) {
 | 
			
		||||
        this->target_temperature = this->codec_->target_temp_;
 | 
			
		||||
      }
 | 
			
		||||
      if (this->codec_->has_current_temp()) {
 | 
			
		||||
        this->current_temperature = this->codec_->current_temp_;
 | 
			
		||||
      }
 | 
			
		||||
      if (this->codec_->has_running()) {
 | 
			
		||||
        this->mode = this->codec_->running_ ? climate::CLIMATE_MODE_HEAT : climate::CLIMATE_MODE_OFF;
 | 
			
		||||
      }
 | 
			
		||||
      this->publish_state();
 | 
			
		||||
 | 
			
		||||
      if (this->current_request_ > 0) {
 | 
			
		||||
        AnovaPacket *pkt = nullptr;
 | 
			
		||||
        switch (this->current_request_++) {
 | 
			
		||||
          case 1:
 | 
			
		||||
            pkt = this->codec_->get_read_target_temp_request();
 | 
			
		||||
            break;
 | 
			
		||||
          case 2:
 | 
			
		||||
            pkt = this->codec_->get_read_current_temp_request();
 | 
			
		||||
            break;
 | 
			
		||||
          default:
 | 
			
		||||
            this->current_request_ = 0;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        if (pkt != nullptr) {
 | 
			
		||||
          auto status =
 | 
			
		||||
              esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length,
 | 
			
		||||
                                       pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
          if (status)
 | 
			
		||||
            ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
 | 
			
		||||
                     status);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Anova::update() {
 | 
			
		||||
  if (this->node_state != espbt::ClientState::Established)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (this->current_request_ == 0) {
 | 
			
		||||
    auto pkt = this->codec_->get_read_device_status_request();
 | 
			
		||||
    auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
 | 
			
		||||
                                           pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status)
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
    this->current_request_++;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace anova
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										50
									
								
								esphome/components/anova/anova.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								esphome/components/anova/anova.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/ble_client/ble_client.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/components/climate/climate.h"
 | 
			
		||||
#include "anova_base.h"
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace anova {
 | 
			
		||||
 | 
			
		||||
namespace espbt = esphome::esp32_ble_tracker;
 | 
			
		||||
 | 
			
		||||
static const uint16_t ANOVA_SERVICE_UUID = 0xFFE0;
 | 
			
		||||
static const uint16_t ANOVA_CHARACTERISTIC_UUID = 0xFFE1;
 | 
			
		||||
 | 
			
		||||
class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode, public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
			
		||||
  climate::ClimateTraits traits() {
 | 
			
		||||
    auto traits = climate::ClimateTraits();
 | 
			
		||||
    traits.set_supports_current_temperature(true);
 | 
			
		||||
    traits.set_supports_heat_mode(true);
 | 
			
		||||
    traits.set_visual_min_temperature(25.0);
 | 
			
		||||
    traits.set_visual_max_temperature(100.0);
 | 
			
		||||
    traits.set_visual_temperature_step(0.1);
 | 
			
		||||
    return traits;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  AnovaCodec *codec_;
 | 
			
		||||
  void control(const climate::ClimateCall &call) override;
 | 
			
		||||
  uint16_t char_handle_;
 | 
			
		||||
  uint8_t current_request_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace anova
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										119
									
								
								esphome/components/anova/anova_base.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								esphome/components/anova/anova_base.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
#include "anova_base.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace anova {
 | 
			
		||||
 | 
			
		||||
AnovaPacket *AnovaCodec::clean_packet_() {
 | 
			
		||||
  this->packet_.length = strlen((char *) this->packet_.data);
 | 
			
		||||
  this->packet_.data[this->packet_.length] = '\0';
 | 
			
		||||
  ESP_LOGV("anova", "SendPkt: %s\n", this->packet_.data);
 | 
			
		||||
  return &this->packet_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AnovaPacket *AnovaCodec::get_read_device_status_request() {
 | 
			
		||||
  this->current_query_ = READ_DEVICE_STATUS;
 | 
			
		||||
  sprintf((char *) this->packet_.data, "%s", CMD_READ_DEVICE_STATUS);
 | 
			
		||||
  return this->clean_packet_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AnovaPacket *AnovaCodec::get_read_target_temp_request() {
 | 
			
		||||
  this->current_query_ = READ_TARGET_TEMPERATURE;
 | 
			
		||||
  sprintf((char *) this->packet_.data, "%s", CMD_READ_TARGET_TEMP);
 | 
			
		||||
  return this->clean_packet_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AnovaPacket *AnovaCodec::get_read_current_temp_request() {
 | 
			
		||||
  this->current_query_ = READ_CURRENT_TEMPERATURE;
 | 
			
		||||
  sprintf((char *) this->packet_.data, "%s", CMD_READ_CURRENT_TEMP);
 | 
			
		||||
  return this->clean_packet_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AnovaPacket *AnovaCodec::get_read_unit_request() {
 | 
			
		||||
  this->current_query_ = READ_UNIT;
 | 
			
		||||
  sprintf((char *) this->packet_.data, "%s", CMD_READ_UNIT);
 | 
			
		||||
  return this->clean_packet_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AnovaPacket *AnovaCodec::get_read_data_request() {
 | 
			
		||||
  this->current_query_ = READ_DATA;
 | 
			
		||||
  sprintf((char *) this->packet_.data, "%s", CMD_READ_DATA);
 | 
			
		||||
  return this->clean_packet_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AnovaPacket *AnovaCodec::get_set_target_temp_request(float temperature) {
 | 
			
		||||
  this->current_query_ = SET_TARGET_TEMPERATURE;
 | 
			
		||||
  sprintf((char *) this->packet_.data, CMD_SET_TARGET_TEMP, temperature);
 | 
			
		||||
  return this->clean_packet_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AnovaPacket *AnovaCodec::get_set_unit_request(char unit) {
 | 
			
		||||
  this->current_query_ = SET_UNIT;
 | 
			
		||||
  sprintf((char *) this->packet_.data, CMD_SET_TEMP_UNIT, unit);
 | 
			
		||||
  return this->clean_packet_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AnovaPacket *AnovaCodec::get_start_request() {
 | 
			
		||||
  this->current_query_ = START;
 | 
			
		||||
  sprintf((char *) this->packet_.data, CMD_START);
 | 
			
		||||
  return this->clean_packet_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AnovaPacket *AnovaCodec::get_stop_request() {
 | 
			
		||||
  this->current_query_ = STOP;
 | 
			
		||||
  sprintf((char *) this->packet_.data, CMD_STOP);
 | 
			
		||||
  return this->clean_packet_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
 | 
			
		||||
  memset(this->buf_, 0, 32);
 | 
			
		||||
  strncpy(this->buf_, (char *) data, length);
 | 
			
		||||
  ESP_LOGV("anova", "Received: %s\n", this->buf_);
 | 
			
		||||
  this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false;
 | 
			
		||||
  switch (this->current_query_) {
 | 
			
		||||
    case READ_DEVICE_STATUS: {
 | 
			
		||||
      if (!strncmp(this->buf_, "stopped", 7)) {
 | 
			
		||||
        this->has_running_ = true;
 | 
			
		||||
        this->running_ = false;
 | 
			
		||||
      }
 | 
			
		||||
      if (!strncmp(this->buf_, "running", 7)) {
 | 
			
		||||
        this->has_running_ = true;
 | 
			
		||||
        this->running_ = true;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case START: {
 | 
			
		||||
      if (!strncmp(this->buf_, "start", 5)) {
 | 
			
		||||
        this->has_running_ = true;
 | 
			
		||||
        this->running_ = true;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case STOP: {
 | 
			
		||||
      if (!strncmp(this->buf_, "stop", 4)) {
 | 
			
		||||
        this->has_running_ = true;
 | 
			
		||||
        this->running_ = false;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case READ_TARGET_TEMPERATURE: {
 | 
			
		||||
      this->target_temp_ = strtof(this->buf_, nullptr);
 | 
			
		||||
      this->has_target_temp_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case SET_TARGET_TEMPERATURE: {
 | 
			
		||||
      this->target_temp_ = strtof(this->buf_, nullptr);
 | 
			
		||||
      this->has_target_temp_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case READ_CURRENT_TEMPERATURE: {
 | 
			
		||||
      this->current_temp_ = strtof(this->buf_, nullptr);
 | 
			
		||||
      this->has_current_temp_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace anova
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										79
									
								
								esphome/components/anova/anova_base.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								esphome/components/anova/anova_base.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace anova {
 | 
			
		||||
 | 
			
		||||
enum CurrentQuery {
 | 
			
		||||
  NONE,
 | 
			
		||||
  READ_DEVICE_STATUS,
 | 
			
		||||
  READ_TARGET_TEMPERATURE,
 | 
			
		||||
  READ_CURRENT_TEMPERATURE,
 | 
			
		||||
  READ_DATA,
 | 
			
		||||
  READ_UNIT,
 | 
			
		||||
  SET_TARGET_TEMPERATURE,
 | 
			
		||||
  SET_UNIT,
 | 
			
		||||
  START,
 | 
			
		||||
  STOP,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct AnovaPacket {
 | 
			
		||||
  uint16_t length;
 | 
			
		||||
  uint8_t data[24];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define CMD_READ_DEVICE_STATUS "status\r"
 | 
			
		||||
#define CMD_READ_TARGET_TEMP "read set temp\r"
 | 
			
		||||
#define CMD_READ_CURRENT_TEMP "read temp\r"
 | 
			
		||||
#define CMD_READ_UNIT "read unit\r"
 | 
			
		||||
#define CMD_READ_DATA "read data\r"
 | 
			
		||||
#define CMD_SET_TARGET_TEMP "set temp %.1f\r"
 | 
			
		||||
#define CMD_SET_TEMP_UNIT "set unit %c\r"
 | 
			
		||||
 | 
			
		||||
#define CMD_START "start\r"
 | 
			
		||||
#define CMD_STOP "stop\r"
 | 
			
		||||
 | 
			
		||||
class AnovaCodec {
 | 
			
		||||
 public:
 | 
			
		||||
  AnovaPacket *get_read_device_status_request();
 | 
			
		||||
  AnovaPacket *get_read_target_temp_request();
 | 
			
		||||
  AnovaPacket *get_read_current_temp_request();
 | 
			
		||||
  AnovaPacket *get_read_data_request();
 | 
			
		||||
  AnovaPacket *get_read_unit_request();
 | 
			
		||||
 | 
			
		||||
  AnovaPacket *get_set_target_temp_request(float temperature);
 | 
			
		||||
  AnovaPacket *get_set_unit_request(char unit);
 | 
			
		||||
 | 
			
		||||
  AnovaPacket *get_start_request();
 | 
			
		||||
  AnovaPacket *get_stop_request();
 | 
			
		||||
 | 
			
		||||
  void decode(const uint8_t *data, uint16_t length);
 | 
			
		||||
  bool has_target_temp() { return this->has_target_temp_; }
 | 
			
		||||
  bool has_current_temp() { return this->has_current_temp_; }
 | 
			
		||||
  bool has_unit() { return this->has_unit_; }
 | 
			
		||||
  bool has_running() { return this->has_running_; }
 | 
			
		||||
 | 
			
		||||
  union {
 | 
			
		||||
    float target_temp_;
 | 
			
		||||
    float current_temp_;
 | 
			
		||||
    char unit_;
 | 
			
		||||
    bool running_;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  AnovaPacket *clean_packet_();
 | 
			
		||||
  AnovaPacket packet_;
 | 
			
		||||
 | 
			
		||||
  bool has_target_temp_;
 | 
			
		||||
  bool has_current_temp_;
 | 
			
		||||
  bool has_unit_;
 | 
			
		||||
  bool has_running_;
 | 
			
		||||
  char buf_[32];
 | 
			
		||||
 | 
			
		||||
  CurrentQuery current_query_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace anova
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										25
									
								
								esphome/components/anova/climate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphome/components/anova/climate.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import climate, ble_client
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@buxtronix"]
 | 
			
		||||
DEPENDENCIES = ["ble_client"]
 | 
			
		||||
 | 
			
		||||
anova_ns = cg.esphome_ns.namespace("anova")
 | 
			
		||||
Anova = anova_ns.class_(
 | 
			
		||||
    "Anova", climate.Climate, ble_client.BLEClientNode, cg.PollingComponent
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    climate.CLIMATE_SCHEMA.extend({cv.GenerateID(): cv.declare_id(Anova)})
 | 
			
		||||
    .extend(ble_client.BLE_CLIENT_SCHEMA)
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield climate.register_climate(var, config)
 | 
			
		||||
    yield ble_client.register_ble_node(var, config)
 | 
			
		||||
@@ -4,10 +4,10 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace apds9960 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "apds9960";
 | 
			
		||||
static const char *const TAG = "apds9960";
 | 
			
		||||
 | 
			
		||||
#define APDS9960_ERROR_CHECK(func) \
 | 
			
		||||
  if (!func) { \
 | 
			
		||||
  if (!(func)) { \
 | 
			
		||||
    this->mark_failed(); \
 | 
			
		||||
    return; \
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ service APIConnection {
 | 
			
		||||
  rpc switch_command (SwitchCommandRequest) returns (void) {}
 | 
			
		||||
  rpc camera_image (CameraImageRequest) returns (void) {}
 | 
			
		||||
  rpc climate_command (ClimateCommandRequest) returns (void) {}
 | 
			
		||||
  rpc number_command (NumberCommandRequest) returns (void) {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -378,6 +379,7 @@ message LightStateResponse {
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  bool state = 2;
 | 
			
		||||
  float brightness = 3;
 | 
			
		||||
  float color_brightness = 10;
 | 
			
		||||
  float red = 4;
 | 
			
		||||
  float green = 5;
 | 
			
		||||
  float blue = 6;
 | 
			
		||||
@@ -396,6 +398,8 @@ message LightCommandRequest {
 | 
			
		||||
  bool state = 3;
 | 
			
		||||
  bool has_brightness = 4;
 | 
			
		||||
  float brightness = 5;
 | 
			
		||||
  bool has_color_brightness = 20;
 | 
			
		||||
  float color_brightness = 21;
 | 
			
		||||
  bool has_rgb = 6;
 | 
			
		||||
  float red = 7;
 | 
			
		||||
  float green = 8;
 | 
			
		||||
@@ -710,13 +714,14 @@ enum ClimateAction {
 | 
			
		||||
  CLIMATE_ACTION_FAN = 6;
 | 
			
		||||
}
 | 
			
		||||
enum ClimatePreset {
 | 
			
		||||
  CLIMATE_PRESET_ECO = 0;
 | 
			
		||||
  CLIMATE_PRESET_AWAY = 1;
 | 
			
		||||
  CLIMATE_PRESET_BOOST = 2;
 | 
			
		||||
  CLIMATE_PRESET_COMFORT = 3;
 | 
			
		||||
  CLIMATE_PRESET_HOME = 4;
 | 
			
		||||
  CLIMATE_PRESET_SLEEP = 5;
 | 
			
		||||
  CLIMATE_PRESET_ACTIVITY = 6;
 | 
			
		||||
  CLIMATE_PRESET_NONE = 0;
 | 
			
		||||
  CLIMATE_PRESET_HOME = 1;
 | 
			
		||||
  CLIMATE_PRESET_AWAY = 2;
 | 
			
		||||
  CLIMATE_PRESET_BOOST = 3;
 | 
			
		||||
  CLIMATE_PRESET_COMFORT = 4;
 | 
			
		||||
  CLIMATE_PRESET_ECO = 5;
 | 
			
		||||
  CLIMATE_PRESET_SLEEP = 6;
 | 
			
		||||
  CLIMATE_PRESET_ACTIVITY = 7;
 | 
			
		||||
}
 | 
			
		||||
message ListEntitiesClimateResponse {
 | 
			
		||||
  option (id) = 46;
 | 
			
		||||
@@ -734,7 +739,9 @@ message ListEntitiesClimateResponse {
 | 
			
		||||
  float visual_min_temperature = 8;
 | 
			
		||||
  float visual_max_temperature = 9;
 | 
			
		||||
  float visual_temperature_step = 10;
 | 
			
		||||
  bool supports_away = 11;
 | 
			
		||||
  // for older peer versions - in new system this
 | 
			
		||||
  // is if CLIMATE_PRESET_AWAY exists is supported_presets
 | 
			
		||||
  bool legacy_supports_away = 11;
 | 
			
		||||
  bool supports_action = 12;
 | 
			
		||||
  repeated ClimateFanMode supported_fan_modes = 13;
 | 
			
		||||
  repeated ClimateSwingMode supported_swing_modes = 14;
 | 
			
		||||
@@ -754,7 +761,8 @@ message ClimateStateResponse {
 | 
			
		||||
  float target_temperature = 4;
 | 
			
		||||
  float target_temperature_low = 5;
 | 
			
		||||
  float target_temperature_high = 6;
 | 
			
		||||
  bool 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;
 | 
			
		||||
@@ -777,8 +785,9 @@ message ClimateCommandRequest {
 | 
			
		||||
  float target_temperature_low = 7;
 | 
			
		||||
  bool has_target_temperature_high = 8;
 | 
			
		||||
  float target_temperature_high = 9;
 | 
			
		||||
  bool has_away = 10;
 | 
			
		||||
  bool 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;
 | 
			
		||||
@@ -790,3 +799,41 @@ message ClimateCommandRequest {
 | 
			
		||||
  bool has_custom_preset = 20;
 | 
			
		||||
  string custom_preset = 21;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== NUMBER ====================
 | 
			
		||||
message ListEntitiesNumberResponse {
 | 
			
		||||
  option (id) = 49;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_NUMBER";
 | 
			
		||||
 | 
			
		||||
  string object_id = 1;
 | 
			
		||||
  fixed32 key = 2;
 | 
			
		||||
  string name = 3;
 | 
			
		||||
  string unique_id = 4;
 | 
			
		||||
 | 
			
		||||
  string icon = 5;
 | 
			
		||||
  float min_value = 6;
 | 
			
		||||
  float max_value = 7;
 | 
			
		||||
  float step = 8;
 | 
			
		||||
}
 | 
			
		||||
message NumberStateResponse {
 | 
			
		||||
  option (id) = 50;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_NUMBER";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  float state = 2;
 | 
			
		||||
  // If the number does not have a valid state yet.
 | 
			
		||||
  // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
 | 
			
		||||
  bool missing_state = 3;
 | 
			
		||||
}
 | 
			
		||||
message NumberCommandRequest {
 | 
			
		||||
  option (id) = 51;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_NUMBER";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  float state = 2;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "api.connection";
 | 
			
		||||
static const char *const TAG = "api.connection";
 | 
			
		||||
 | 
			
		||||
APIConnection::APIConnection(AsyncClient *client, APIServer *parent)
 | 
			
		||||
    : client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
 | 
			
		||||
@@ -308,6 +308,7 @@ bool APIConnection::send_light_state(light::LightState *light) {
 | 
			
		||||
  if (traits.get_supports_brightness())
 | 
			
		||||
    resp.brightness = values.get_brightness();
 | 
			
		||||
  if (traits.get_supports_rgb()) {
 | 
			
		||||
    resp.color_brightness = values.get_color_brightness();
 | 
			
		||||
    resp.red = values.get_red();
 | 
			
		||||
    resp.green = values.get_green();
 | 
			
		||||
    resp.blue = values.get_blue();
 | 
			
		||||
@@ -352,6 +353,8 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
 | 
			
		||||
    call.set_state(msg.state);
 | 
			
		||||
  if (msg.has_brightness)
 | 
			
		||||
    call.set_brightness(msg.brightness);
 | 
			
		||||
  if (msg.has_color_brightness)
 | 
			
		||||
    call.set_color_brightness(msg.color_brightness);
 | 
			
		||||
  if (msg.has_rgb) {
 | 
			
		||||
    call.set_red(msg.red);
 | 
			
		||||
    call.set_green(msg.green);
 | 
			
		||||
@@ -475,14 +478,14 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
 | 
			
		||||
  } else {
 | 
			
		||||
    resp.target_temperature = climate->target_temperature;
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_away())
 | 
			
		||||
    resp.away = climate->away;
 | 
			
		||||
  if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
 | 
			
		||||
    resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
 | 
			
		||||
  if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value())
 | 
			
		||||
    resp.custom_fan_mode = climate->custom_fan_mode.value();
 | 
			
		||||
  if (traits.get_supports_presets() && climate->preset.has_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();
 | 
			
		||||
  if (traits.get_supports_swing_modes())
 | 
			
		||||
@@ -498,40 +501,26 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
 | 
			
		||||
  msg.unique_id = get_default_unique_id("climate", climate);
 | 
			
		||||
  msg.supports_current_temperature = traits.get_supports_current_temperature();
 | 
			
		||||
  msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
 | 
			
		||||
  for (auto mode :
 | 
			
		||||
       {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT,
 | 
			
		||||
        climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_HEAT_COOL}) {
 | 
			
		||||
    if (traits.supports_mode(mode))
 | 
			
		||||
      msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (auto mode : traits.get_supported_modes())
 | 
			
		||||
    msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
 | 
			
		||||
 | 
			
		||||
  msg.visual_min_temperature = traits.get_visual_min_temperature();
 | 
			
		||||
  msg.visual_max_temperature = traits.get_visual_max_temperature();
 | 
			
		||||
  msg.visual_temperature_step = traits.get_visual_temperature_step();
 | 
			
		||||
  msg.supports_away = traits.get_supports_away();
 | 
			
		||||
  msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
 | 
			
		||||
  msg.supports_action = traits.get_supports_action();
 | 
			
		||||
  for (auto fan_mode : {climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_AUTO,
 | 
			
		||||
                        climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH,
 | 
			
		||||
                        climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE}) {
 | 
			
		||||
    if (traits.supports_fan_mode(fan_mode))
 | 
			
		||||
      msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
 | 
			
		||||
  }
 | 
			
		||||
  for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) {
 | 
			
		||||
 | 
			
		||||
  for (auto fan_mode : traits.get_supported_fan_modes())
 | 
			
		||||
    msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
 | 
			
		||||
  for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
 | 
			
		||||
    msg.supported_custom_fan_modes.push_back(custom_fan_mode);
 | 
			
		||||
  }
 | 
			
		||||
  for (auto preset : {climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_AWAY, climate::CLIMATE_PRESET_BOOST,
 | 
			
		||||
                      climate::CLIMATE_PRESET_COMFORT, climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_SLEEP,
 | 
			
		||||
                      climate::CLIMATE_PRESET_ACTIVITY}) {
 | 
			
		||||
    if (traits.supports_preset(preset))
 | 
			
		||||
      msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset));
 | 
			
		||||
  }
 | 
			
		||||
  for (auto const &custom_preset : traits.get_supported_custom_presets()) {
 | 
			
		||||
  for (auto preset : traits.get_supported_presets())
 | 
			
		||||
    msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset));
 | 
			
		||||
  for (auto const &custom_preset : traits.get_supported_custom_presets())
 | 
			
		||||
    msg.supported_custom_presets.push_back(custom_preset);
 | 
			
		||||
  }
 | 
			
		||||
  for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL,
 | 
			
		||||
                          climate::CLIMATE_SWING_HORIZONTAL}) {
 | 
			
		||||
    if (traits.supports_swing_mode(swing_mode))
 | 
			
		||||
      msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
 | 
			
		||||
  }
 | 
			
		||||
  for (auto swing_mode : traits.get_supported_swing_modes())
 | 
			
		||||
    msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
 | 
			
		||||
  return this->send_list_entities_climate_response(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
 | 
			
		||||
@@ -548,8 +537,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_away)
 | 
			
		||||
    call.set_away(msg.away);
 | 
			
		||||
  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)
 | 
			
		||||
@@ -564,6 +553,42 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
bool APIConnection::send_number_state(number::Number *number, float state) {
 | 
			
		||||
  if (!this->state_subscription_)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  NumberStateResponse resp{};
 | 
			
		||||
  resp.key = number->get_object_id_hash();
 | 
			
		||||
  resp.state = state;
 | 
			
		||||
  resp.missing_state = !number->has_state();
 | 
			
		||||
  return this->send_number_state_response(resp);
 | 
			
		||||
}
 | 
			
		||||
bool APIConnection::send_number_info(number::Number *number) {
 | 
			
		||||
  ListEntitiesNumberResponse msg;
 | 
			
		||||
  msg.key = number->get_object_id_hash();
 | 
			
		||||
  msg.object_id = number->get_object_id();
 | 
			
		||||
  msg.name = number->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("number", number);
 | 
			
		||||
  msg.icon = number->traits.get_icon();
 | 
			
		||||
 | 
			
		||||
  msg.min_value = number->traits.get_min_value();
 | 
			
		||||
  msg.max_value = number->traits.get_max_value();
 | 
			
		||||
  msg.step = number->traits.get_step();
 | 
			
		||||
 | 
			
		||||
  return this->send_list_entities_number_response(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::number_command(const NumberCommandRequest &msg) {
 | 
			
		||||
  number::Number *number = App.get_number_by_key(msg.key);
 | 
			
		||||
  if (number == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  auto call = number->make_call();
 | 
			
		||||
  call.set_value(msg.state);
 | 
			
		||||
  call.perform();
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
 | 
			
		||||
  if (!this->state_subscription_)
 | 
			
		||||
@@ -629,7 +654,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
 | 
			
		||||
 | 
			
		||||
  HelloResponse resp;
 | 
			
		||||
  resp.api_version_major = 1;
 | 
			
		||||
  resp.api_version_minor = 4;
 | 
			
		||||
  resp.api_version_minor = 5;
 | 
			
		||||
  resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
 | 
			
		||||
  this->connection_state_ = ConnectionState::CONNECTED;
 | 
			
		||||
  return resp;
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,11 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  bool send_climate_state(climate::Climate *climate);
 | 
			
		||||
  bool send_climate_info(climate::Climate *climate);
 | 
			
		||||
  void climate_command(const ClimateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  bool send_number_state(number::Number *number, float state);
 | 
			
		||||
  bool send_number_info(number::Number *number);
 | 
			
		||||
  void number_command(const NumberCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
  bool send_log_message(int level, const char *tag, const char *line);
 | 
			
		||||
  void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
 | 
			
		||||
 
 | 
			
		||||
@@ -192,16 +192,18 @@ template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::Climate
 | 
			
		||||
}
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::ClimatePreset value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::CLIMATE_PRESET_ECO:
 | 
			
		||||
      return "CLIMATE_PRESET_ECO";
 | 
			
		||||
    case enums::CLIMATE_PRESET_NONE:
 | 
			
		||||
      return "CLIMATE_PRESET_NONE";
 | 
			
		||||
    case enums::CLIMATE_PRESET_HOME:
 | 
			
		||||
      return "CLIMATE_PRESET_HOME";
 | 
			
		||||
    case enums::CLIMATE_PRESET_AWAY:
 | 
			
		||||
      return "CLIMATE_PRESET_AWAY";
 | 
			
		||||
    case enums::CLIMATE_PRESET_BOOST:
 | 
			
		||||
      return "CLIMATE_PRESET_BOOST";
 | 
			
		||||
    case enums::CLIMATE_PRESET_COMFORT:
 | 
			
		||||
      return "CLIMATE_PRESET_COMFORT";
 | 
			
		||||
    case enums::CLIMATE_PRESET_HOME:
 | 
			
		||||
      return "CLIMATE_PRESET_HOME";
 | 
			
		||||
    case enums::CLIMATE_PRESET_ECO:
 | 
			
		||||
      return "CLIMATE_PRESET_ECO";
 | 
			
		||||
    case enums::CLIMATE_PRESET_SLEEP:
 | 
			
		||||
      return "CLIMATE_PRESET_SLEEP";
 | 
			
		||||
    case enums::CLIMATE_PRESET_ACTIVITY:
 | 
			
		||||
@@ -1261,6 +1263,10 @@ bool LightStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
      this->brightness = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 10: {
 | 
			
		||||
      this->color_brightness = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->red = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
@@ -1289,6 +1295,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_fixed32(1, this->key);
 | 
			
		||||
  buffer.encode_bool(2, this->state);
 | 
			
		||||
  buffer.encode_float(3, this->brightness);
 | 
			
		||||
  buffer.encode_float(10, this->color_brightness);
 | 
			
		||||
  buffer.encode_float(4, this->red);
 | 
			
		||||
  buffer.encode_float(5, this->green);
 | 
			
		||||
  buffer.encode_float(6, this->blue);
 | 
			
		||||
@@ -1313,6 +1320,11 @@ void LightStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  color_brightness: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->color_brightness);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  red: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->red);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
@@ -1357,6 +1369,10 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
      this->has_brightness = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 20: {
 | 
			
		||||
      this->has_color_brightness = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 6: {
 | 
			
		||||
      this->has_rgb = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
@@ -1413,6 +1429,10 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
      this->brightness = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 21: {
 | 
			
		||||
      this->color_brightness = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->red = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
@@ -1443,6 +1463,8 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_bool(3, this->state);
 | 
			
		||||
  buffer.encode_bool(4, this->has_brightness);
 | 
			
		||||
  buffer.encode_float(5, this->brightness);
 | 
			
		||||
  buffer.encode_bool(20, this->has_color_brightness);
 | 
			
		||||
  buffer.encode_float(21, this->color_brightness);
 | 
			
		||||
  buffer.encode_bool(6, this->has_rgb);
 | 
			
		||||
  buffer.encode_float(7, this->red);
 | 
			
		||||
  buffer.encode_float(8, this->green);
 | 
			
		||||
@@ -1483,6 +1505,15 @@ void LightCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_color_brightness: ");
 | 
			
		||||
  out.append(YESNO(this->has_color_brightness));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  color_brightness: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->color_brightness);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_rgb: ");
 | 
			
		||||
  out.append(YESNO(this->has_rgb));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
@@ -2672,7 +2703,7 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 11: {
 | 
			
		||||
      this->supports_away = value.as_bool();
 | 
			
		||||
      this->legacy_supports_away = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 12: {
 | 
			
		||||
@@ -2756,7 +2787,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_float(8, this->visual_min_temperature);
 | 
			
		||||
  buffer.encode_float(9, this->visual_max_temperature);
 | 
			
		||||
  buffer.encode_float(10, this->visual_temperature_step);
 | 
			
		||||
  buffer.encode_bool(11, this->supports_away);
 | 
			
		||||
  buffer.encode_bool(11, this->legacy_supports_away);
 | 
			
		||||
  buffer.encode_bool(12, this->supports_action);
 | 
			
		||||
  for (auto &it : this->supported_fan_modes) {
 | 
			
		||||
    buffer.encode_enum<enums::ClimateFanMode>(13, it, true);
 | 
			
		||||
@@ -2823,8 +2854,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  supports_away: ");
 | 
			
		||||
  out.append(YESNO(this->supports_away));
 | 
			
		||||
  out.append("  legacy_supports_away: ");
 | 
			
		||||
  out.append(YESNO(this->legacy_supports_away));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  supports_action: ");
 | 
			
		||||
@@ -2869,7 +2900,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->away = value.as_bool();
 | 
			
		||||
      this->legacy_away = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
@@ -2939,7 +2970,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_float(4, this->target_temperature);
 | 
			
		||||
  buffer.encode_float(5, this->target_temperature_low);
 | 
			
		||||
  buffer.encode_float(6, this->target_temperature_high);
 | 
			
		||||
  buffer.encode_bool(7, this->away);
 | 
			
		||||
  buffer.encode_bool(7, this->legacy_away);
 | 
			
		||||
  buffer.encode_enum<enums::ClimateAction>(8, this->action);
 | 
			
		||||
  buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode);
 | 
			
		||||
  buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode);
 | 
			
		||||
@@ -2979,8 +3010,8 @@ void ClimateStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  away: ");
 | 
			
		||||
  out.append(YESNO(this->away));
 | 
			
		||||
  out.append("  legacy_away: ");
 | 
			
		||||
  out.append(YESNO(this->legacy_away));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  action: ");
 | 
			
		||||
@@ -3031,11 +3062,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 10: {
 | 
			
		||||
      this->has_away = value.as_bool();
 | 
			
		||||
      this->has_legacy_away = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 11: {
 | 
			
		||||
      this->away = value.as_bool();
 | 
			
		||||
      this->legacy_away = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 12: {
 | 
			
		||||
@@ -3120,8 +3151,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_float(7, this->target_temperature_low);
 | 
			
		||||
  buffer.encode_bool(8, this->has_target_temperature_high);
 | 
			
		||||
  buffer.encode_float(9, this->target_temperature_high);
 | 
			
		||||
  buffer.encode_bool(10, this->has_away);
 | 
			
		||||
  buffer.encode_bool(11, this->away);
 | 
			
		||||
  buffer.encode_bool(10, this->has_legacy_away);
 | 
			
		||||
  buffer.encode_bool(11, this->legacy_away);
 | 
			
		||||
  buffer.encode_bool(12, this->has_fan_mode);
 | 
			
		||||
  buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode);
 | 
			
		||||
  buffer.encode_bool(14, this->has_swing_mode);
 | 
			
		||||
@@ -3176,12 +3207,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_away: ");
 | 
			
		||||
  out.append(YESNO(this->has_away));
 | 
			
		||||
  out.append("  has_legacy_away: ");
 | 
			
		||||
  out.append(YESNO(this->has_legacy_away));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  away: ");
 | 
			
		||||
  out.append(YESNO(this->away));
 | 
			
		||||
  out.append("  legacy_away: ");
 | 
			
		||||
  out.append(YESNO(this->legacy_away));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_fan_mode: ");
 | 
			
		||||
@@ -3225,6 +3256,179 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->object_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->name = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->unique_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 5: {
 | 
			
		||||
      this->icon = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ListEntitiesNumberResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 6: {
 | 
			
		||||
      this->min_value = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->max_value = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
      this->step = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(1, this->object_id);
 | 
			
		||||
  buffer.encode_fixed32(2, this->key);
 | 
			
		||||
  buffer.encode_string(3, this->name);
 | 
			
		||||
  buffer.encode_string(4, this->unique_id);
 | 
			
		||||
  buffer.encode_string(5, this->icon);
 | 
			
		||||
  buffer.encode_float(6, this->min_value);
 | 
			
		||||
  buffer.encode_float(7, this->max_value);
 | 
			
		||||
  buffer.encode_float(8, this->step);
 | 
			
		||||
}
 | 
			
		||||
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesNumberResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  name: ");
 | 
			
		||||
  out.append("'").append(this->name).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  unique_id: ");
 | 
			
		||||
  out.append("'").append(this->unique_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  icon: ");
 | 
			
		||||
  out.append("'").append(this->icon).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  min_value: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->min_value);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  max_value: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->max_value);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  step: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->step);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
bool NumberStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->missing_state = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool NumberStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->state = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void NumberStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_fixed32(1, this->key);
 | 
			
		||||
  buffer.encode_float(2, this->state);
 | 
			
		||||
  buffer.encode_bool(3, this->missing_state);
 | 
			
		||||
}
 | 
			
		||||
void NumberStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  out.append("NumberStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  state: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->state);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  missing_state: ");
 | 
			
		||||
  out.append(YESNO(this->missing_state));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->state = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_fixed32(1, this->key);
 | 
			
		||||
  buffer.encode_float(2, this->state);
 | 
			
		||||
}
 | 
			
		||||
void NumberCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
  out.append("NumberCommandRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  state: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->state);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -90,13 +90,14 @@ enum ClimateAction : uint32_t {
 | 
			
		||||
  CLIMATE_ACTION_FAN = 6,
 | 
			
		||||
};
 | 
			
		||||
enum ClimatePreset : uint32_t {
 | 
			
		||||
  CLIMATE_PRESET_ECO = 0,
 | 
			
		||||
  CLIMATE_PRESET_AWAY = 1,
 | 
			
		||||
  CLIMATE_PRESET_BOOST = 2,
 | 
			
		||||
  CLIMATE_PRESET_COMFORT = 3,
 | 
			
		||||
  CLIMATE_PRESET_HOME = 4,
 | 
			
		||||
  CLIMATE_PRESET_SLEEP = 5,
 | 
			
		||||
  CLIMATE_PRESET_ACTIVITY = 6,
 | 
			
		||||
  CLIMATE_PRESET_NONE = 0,
 | 
			
		||||
  CLIMATE_PRESET_HOME = 1,
 | 
			
		||||
  CLIMATE_PRESET_AWAY = 2,
 | 
			
		||||
  CLIMATE_PRESET_BOOST = 3,
 | 
			
		||||
  CLIMATE_PRESET_COMFORT = 4,
 | 
			
		||||
  CLIMATE_PRESET_ECO = 5,
 | 
			
		||||
  CLIMATE_PRESET_SLEEP = 6,
 | 
			
		||||
  CLIMATE_PRESET_ACTIVITY = 7,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace enums
 | 
			
		||||
@@ -370,6 +371,7 @@ class LightStateResponse : public ProtoMessage {
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  bool state{false};
 | 
			
		||||
  float brightness{0.0f};
 | 
			
		||||
  float color_brightness{0.0f};
 | 
			
		||||
  float red{0.0f};
 | 
			
		||||
  float green{0.0f};
 | 
			
		||||
  float blue{0.0f};
 | 
			
		||||
@@ -391,6 +393,8 @@ class LightCommandRequest : public ProtoMessage {
 | 
			
		||||
  bool state{false};
 | 
			
		||||
  bool has_brightness{false};
 | 
			
		||||
  float brightness{0.0f};
 | 
			
		||||
  bool has_color_brightness{false};
 | 
			
		||||
  float color_brightness{0.0f};
 | 
			
		||||
  bool has_rgb{false};
 | 
			
		||||
  float red{0.0f};
 | 
			
		||||
  float green{0.0f};
 | 
			
		||||
@@ -709,7 +713,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
 | 
			
		||||
  float visual_min_temperature{0.0f};
 | 
			
		||||
  float visual_max_temperature{0.0f};
 | 
			
		||||
  float visual_temperature_step{0.0f};
 | 
			
		||||
  bool supports_away{false};
 | 
			
		||||
  bool legacy_supports_away{false};
 | 
			
		||||
  bool supports_action{false};
 | 
			
		||||
  std::vector<enums::ClimateFanMode> supported_fan_modes{};
 | 
			
		||||
  std::vector<enums::ClimateSwingMode> supported_swing_modes{};
 | 
			
		||||
@@ -732,7 +736,7 @@ class ClimateStateResponse : public ProtoMessage {
 | 
			
		||||
  float target_temperature{0.0f};
 | 
			
		||||
  float target_temperature_low{0.0f};
 | 
			
		||||
  float target_temperature_high{0.0f};
 | 
			
		||||
  bool away{false};
 | 
			
		||||
  bool legacy_away{false};
 | 
			
		||||
  enums::ClimateAction action{};
 | 
			
		||||
  enums::ClimateFanMode fan_mode{};
 | 
			
		||||
  enums::ClimateSwingMode swing_mode{};
 | 
			
		||||
@@ -758,8 +762,8 @@ class ClimateCommandRequest : public ProtoMessage {
 | 
			
		||||
  float target_temperature_low{0.0f};
 | 
			
		||||
  bool has_target_temperature_high{false};
 | 
			
		||||
  float target_temperature_high{0.0f};
 | 
			
		||||
  bool has_away{false};
 | 
			
		||||
  bool away{false};
 | 
			
		||||
  bool has_legacy_away{false};
 | 
			
		||||
  bool legacy_away{false};
 | 
			
		||||
  bool has_fan_mode{false};
 | 
			
		||||
  enums::ClimateFanMode fan_mode{};
 | 
			
		||||
  bool has_swing_mode{false};
 | 
			
		||||
@@ -778,6 +782,45 @@ class ClimateCommandRequest : public ProtoMessage {
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class ListEntitiesNumberResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::string object_id{};
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  std::string name{};
 | 
			
		||||
  std::string unique_id{};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  float min_value{0.0f};
 | 
			
		||||
  float max_value{0.0f};
 | 
			
		||||
  float step{0.0f};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
};
 | 
			
		||||
class NumberStateResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  float state{0.0f};
 | 
			
		||||
  bool missing_state{false};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class NumberCommandRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  float state{0.0f};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "api.service";
 | 
			
		||||
static const char *const TAG = "api.service";
 | 
			
		||||
 | 
			
		||||
bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) {
 | 
			
		||||
  ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str());
 | 
			
		||||
@@ -184,6 +184,20 @@ bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResp
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
bool APIServerConnectionBase::send_list_entities_number_response(const ListEntitiesNumberResponse &msg) {
 | 
			
		||||
  ESP_LOGVV(TAG, "send_list_entities_number_response: %s", msg.dump().c_str());
 | 
			
		||||
  return this->send_message_<ListEntitiesNumberResponse>(msg, 49);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
bool APIServerConnectionBase::send_number_state_response(const NumberStateResponse &msg) {
 | 
			
		||||
  ESP_LOGVV(TAG, "send_number_state_response: %s", msg.dump().c_str());
 | 
			
		||||
  return this->send_message_<NumberStateResponse>(msg, 50);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
#endif
 | 
			
		||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
 | 
			
		||||
  switch (msg_type) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
@@ -349,6 +363,15 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
      ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str());
 | 
			
		||||
      this->on_climate_command_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 51: {
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
      NumberCommandRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
      ESP_LOGVV(TAG, "on_number_command_request: %s", msg.dump().c_str());
 | 
			
		||||
      this->on_number_command_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -547,6 +570,19 @@ void APIServerConnection::on_climate_command_request(const ClimateCommandRequest
 | 
			
		||||
  this->climate_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->number_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -111,6 +111,15 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
  virtual void on_climate_command_request(const ClimateCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  bool send_list_entities_number_response(const ListEntitiesNumberResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  bool send_number_state_response(const NumberStateResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  virtual void on_number_command_request(const NumberCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
 | 
			
		||||
@@ -147,6 +156,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
  virtual void climate_command(const ClimateCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  virtual void number_command(const NumberCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  void on_hello_request(const HelloRequest &msg) override;
 | 
			
		||||
@@ -179,6 +191,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
  void on_climate_command_request(const ClimateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  void on_number_command_request(const NumberCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "api";
 | 
			
		||||
static const char *const TAG = "api";
 | 
			
		||||
 | 
			
		||||
// APIServer
 | 
			
		||||
void APIServer::setup() {
 | 
			
		||||
@@ -180,7 +180,7 @@ void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) {
 | 
			
		||||
void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
 | 
			
		||||
  if (obj->is_internal())
 | 
			
		||||
    return;
 | 
			
		||||
  for (auto *c : this->clients_)
 | 
			
		||||
@@ -197,9 +197,18 @@ void APIServer::on_climate_update(climate::Climate *obj) {
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
void APIServer::on_number_update(number::Number *obj, float state) {
 | 
			
		||||
  if (obj->is_internal())
 | 
			
		||||
    return;
 | 
			
		||||
  for (auto *c : this->clients_)
 | 
			
		||||
    c->send_number_state(obj, state);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
 | 
			
		||||
APIServer *global_api_server = nullptr;
 | 
			
		||||
APIServer *global_api_server = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
 | 
			
		||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
 | 
			
		||||
 
 | 
			
		||||
@@ -56,10 +56,13 @@ class APIServer : public Component, public Controller {
 | 
			
		||||
  void on_switch_update(switch_::Switch *obj, bool state) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  void on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) override;
 | 
			
		||||
  void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
  void on_climate_update(climate::Climate *obj) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  void on_number_update(number::Number *obj, float state) override;
 | 
			
		||||
#endif
 | 
			
		||||
  void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
 | 
			
		||||
  void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
 | 
			
		||||
@@ -91,7 +94,7 @@ class APIServer : public Component, public Controller {
 | 
			
		||||
  std::vector<UserServiceDescriptor *> user_services_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern APIServer *global_api_server;
 | 
			
		||||
extern APIServer *global_api_server;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
 
 | 
			
		||||
@@ -51,5 +51,9 @@ bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
 | 
			
		||||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,9 @@ class ListEntitiesIterator : public ComponentIterator {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
  bool on_climate(climate::Climate *climate) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  bool on_number(number::Number *number) override;
 | 
			
		||||
#endif
 | 
			
		||||
  bool on_end() override;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "api.proto";
 | 
			
		||||
static const char *const TAG = "api.proto";
 | 
			
		||||
 | 
			
		||||
void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
 | 
			
		||||
  uint32_t i = 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,11 @@ bool InitialStateIterator::on_text_sensor(text_sensor::TextSensor *text_sensor)
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); }
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
bool InitialStateIterator::on_number(number::Number *number) {
 | 
			
		||||
  return this->client_->send_number_state(number, number->state);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
 | 
			
		||||
    : ComponentIterator(server), client_(client) {}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,9 @@ class InitialStateIterator : public ComponentIterator {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
  bool on_climate(climate::Climate *climate) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  bool on_number(number::Number *number) override;
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  APIConnection *client_;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "api_pb2.h"
 | 
			
		||||
@@ -20,8 +22,8 @@ template<typename T> enums::ServiceArgType to_service_arg_type();
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
 | 
			
		||||
 public:
 | 
			
		||||
  UserServiceBase(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
 | 
			
		||||
      : name_(name), arg_names_(arg_names) {
 | 
			
		||||
  UserServiceBase(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
 | 
			
		||||
      : name_(std::move(name)), arg_names_(arg_names) {
 | 
			
		||||
    this->key_ = fnv1_hash(this->name_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -167,6 +167,21 @@ void ComponentIterator::advance() {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
    case IteratorState::NUMBER:
 | 
			
		||||
      if (this->at_ >= App.get_numbers().size()) {
 | 
			
		||||
        advance_platform = true;
 | 
			
		||||
      } else {
 | 
			
		||||
        auto *number = App.get_numbers()[this->at_];
 | 
			
		||||
        if (number->is_internal()) {
 | 
			
		||||
          success = true;
 | 
			
		||||
          break;
 | 
			
		||||
        } else {
 | 
			
		||||
          success = this->on_number(number);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
#endif
 | 
			
		||||
    case IteratorState::MAX:
 | 
			
		||||
      if (this->on_end()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,9 @@ class ComponentIterator {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
  virtual bool on_climate(climate::Climate *climate) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  virtual bool on_number(number::Number *number) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
  virtual bool on_end();
 | 
			
		||||
 | 
			
		||||
@@ -81,6 +84,9 @@ class ComponentIterator {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
    CLIMATE,
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
    NUMBER,
 | 
			
		||||
#endif
 | 
			
		||||
    MAX,
 | 
			
		||||
  } state_{IteratorState::NONE};
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace as3935 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "as3935";
 | 
			
		||||
static const char *const TAG = "as3935";
 | 
			
		||||
 | 
			
		||||
void AS3935Component::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up AS3935...");
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace as3935_i2c {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "as3935_i2c";
 | 
			
		||||
static const char *const TAG = "as3935_i2c";
 | 
			
		||||
 | 
			
		||||
void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_pos) {
 | 
			
		||||
  uint8_t write_reg;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace as3935_spi {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "as3935_spi";
 | 
			
		||||
static const char *const TAG = "as3935_spi";
 | 
			
		||||
 | 
			
		||||
void SPIAS3935Component::setup() {
 | 
			
		||||
  ESP_LOGI(TAG, "SPIAS3935Component setup started!");
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atc_mithermometer {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "atc_mithermometer";
 | 
			
		||||
static const char *const TAG = "atc_mithermometer";
 | 
			
		||||
 | 
			
		||||
void ATCMiThermometer::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ATC MiThermometer");
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atm90e32 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "atm90e32";
 | 
			
		||||
static const char *const TAG = "atm90e32";
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::update() {
 | 
			
		||||
  if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace b_parasite {
 | 
			
		||||
 | 
			
		||||
static const char* TAG = "b_parasite";
 | 
			
		||||
static const char *const TAG = "b_parasite";
 | 
			
		||||
 | 
			
		||||
void BParasite::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "b_parasite");
 | 
			
		||||
@@ -16,25 +16,25 @@ void BParasite::dump_config() {
 | 
			
		||||
  LOG_SENSOR("  ", "Soil Moisture", this->soil_moisture_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice& device) {
 | 
			
		||||
bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
			
		||||
  if (device.address_uint64() != address_) {
 | 
			
		||||
    ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
 | 
			
		||||
  const auto& service_datas = device.get_service_datas();
 | 
			
		||||
  const auto &service_datas = device.get_service_datas();
 | 
			
		||||
  if (service_datas.size() != 1) {
 | 
			
		||||
    ESP_LOGE(TAG, "Unexpected service_datas size (%d)", service_datas.size());
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  const auto& service_data = service_datas[0];
 | 
			
		||||
  const auto &service_data = service_datas[0];
 | 
			
		||||
 | 
			
		||||
  ESP_LOGVV(TAG, "Service data:");
 | 
			
		||||
  for (const uint8_t byte : service_data.data) {
 | 
			
		||||
    ESP_LOGVV(TAG, "0x%02x", byte);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const auto& data = service_data.data;
 | 
			
		||||
  const auto &data = service_data.data;
 | 
			
		||||
 | 
			
		||||
  // Counter for deduplicating messages.
 | 
			
		||||
  uint8_t counter = data[1] & 0x0f;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										239
									
								
								esphome/components/ballu/ballu.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								esphome/components/ballu/ballu.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,239 @@
 | 
			
		||||
#include "ballu.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ballu {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ballu.climate";
 | 
			
		||||
 | 
			
		||||
const uint16_t BALLU_HEADER_MARK = 9000;
 | 
			
		||||
const uint16_t BALLU_HEADER_SPACE = 4500;
 | 
			
		||||
const uint16_t BALLU_BIT_MARK = 575;
 | 
			
		||||
const uint16_t BALLU_ONE_SPACE = 1675;
 | 
			
		||||
const uint16_t BALLU_ZERO_SPACE = 550;
 | 
			
		||||
 | 
			
		||||
const uint32_t BALLU_CARRIER_FREQUENCY = 38000;
 | 
			
		||||
 | 
			
		||||
const uint8_t BALLU_STATE_LENGTH = 13;
 | 
			
		||||
 | 
			
		||||
const uint8_t BALLU_AUTO = 0;
 | 
			
		||||
const uint8_t BALLU_COOL = 0x20;
 | 
			
		||||
const uint8_t BALLU_DRY = 0x40;
 | 
			
		||||
const uint8_t BALLU_HEAT = 0x80;
 | 
			
		||||
const uint8_t BALLU_FAN = 0xc0;
 | 
			
		||||
 | 
			
		||||
const uint8_t BALLU_FAN_AUTO = 0xa0;
 | 
			
		||||
const uint8_t BALLU_FAN_HIGH = 0x20;
 | 
			
		||||
const uint8_t BALLU_FAN_MED = 0x40;
 | 
			
		||||
const uint8_t BALLU_FAN_LOW = 0x60;
 | 
			
		||||
 | 
			
		||||
const uint8_t BALLU_SWING_VER = 0x07;
 | 
			
		||||
const uint8_t BALLU_SWING_HOR = 0xe0;
 | 
			
		||||
const uint8_t BALLU_POWER = 0x20;
 | 
			
		||||
 | 
			
		||||
void BalluClimate::transmit_state() {
 | 
			
		||||
  uint8_t remote_state[BALLU_STATE_LENGTH] = {0};
 | 
			
		||||
 | 
			
		||||
  auto temp = (uint8_t) roundf(clamp(this->target_temperature, YKR_K_002E_TEMP_MIN, YKR_K_002E_TEMP_MAX));
 | 
			
		||||
  auto swing_ver =
 | 
			
		||||
      ((this->swing_mode == climate::CLIMATE_SWING_VERTICAL) || (this->swing_mode == climate::CLIMATE_SWING_BOTH));
 | 
			
		||||
  auto swing_hor =
 | 
			
		||||
      ((this->swing_mode == climate::CLIMATE_SWING_HORIZONTAL) || (this->swing_mode == climate::CLIMATE_SWING_BOTH));
 | 
			
		||||
 | 
			
		||||
  remote_state[0] = 0xc3;
 | 
			
		||||
  remote_state[1] = ((temp - 8) << 3) | (swing_ver ? 0 : BALLU_SWING_VER);
 | 
			
		||||
  remote_state[2] = swing_hor ? 0 : BALLU_SWING_HOR;
 | 
			
		||||
  remote_state[9] = (this->mode == climate::CLIMATE_MODE_OFF) ? 0 : BALLU_POWER;
 | 
			
		||||
  remote_state[11] = 0x1e;
 | 
			
		||||
 | 
			
		||||
  // Fan speed
 | 
			
		||||
  switch (this->fan_mode.value()) {
 | 
			
		||||
    case climate::CLIMATE_FAN_HIGH:
 | 
			
		||||
      remote_state[4] |= BALLU_FAN_HIGH;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_MEDIUM:
 | 
			
		||||
      remote_state[4] |= BALLU_FAN_MED;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_LOW:
 | 
			
		||||
      remote_state[4] |= BALLU_FAN_LOW;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_AUTO:
 | 
			
		||||
      remote_state[4] |= BALLU_FAN_AUTO;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Mode
 | 
			
		||||
  switch (this->mode) {
 | 
			
		||||
    case climate::CLIMATE_MODE_AUTO:
 | 
			
		||||
      remote_state[6] |= BALLU_AUTO;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_HEAT:
 | 
			
		||||
      remote_state[6] |= BALLU_HEAT;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_COOL:
 | 
			
		||||
      remote_state[6] |= BALLU_COOL;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_DRY:
 | 
			
		||||
      remote_state[6] |= BALLU_DRY;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_FAN_ONLY:
 | 
			
		||||
      remote_state[6] |= BALLU_FAN;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_OFF:
 | 
			
		||||
      remote_state[6] |= BALLU_AUTO;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Checksum
 | 
			
		||||
  for (uint8_t i = 0; i < BALLU_STATE_LENGTH - 1; i++)
 | 
			
		||||
    remote_state[12] += remote_state[i];
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Sending: %02X %02X %02X %02X   %02X %02X %02X %02X   %02X %02X %02X %02X   %02X", remote_state[0],
 | 
			
		||||
           remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6],
 | 
			
		||||
           remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], remote_state[12]);
 | 
			
		||||
 | 
			
		||||
  // Send code
 | 
			
		||||
  auto transmit = this->transmitter_->transmit();
 | 
			
		||||
  auto data = transmit.get_data();
 | 
			
		||||
 | 
			
		||||
  data->set_carrier_frequency(38000);
 | 
			
		||||
 | 
			
		||||
  // Header
 | 
			
		||||
  data->mark(BALLU_HEADER_MARK);
 | 
			
		||||
  data->space(BALLU_HEADER_SPACE);
 | 
			
		||||
  // Data
 | 
			
		||||
  for (uint8_t i : remote_state) {
 | 
			
		||||
    for (uint8_t j = 0; j < 8; j++) {
 | 
			
		||||
      data->mark(BALLU_BIT_MARK);
 | 
			
		||||
      bool bit = i & (1 << j);
 | 
			
		||||
      data->space(bit ? BALLU_ONE_SPACE : BALLU_ZERO_SPACE);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // Footer
 | 
			
		||||
  data->mark(BALLU_BIT_MARK);
 | 
			
		||||
 | 
			
		||||
  transmit.perform();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BalluClimate::on_receive(remote_base::RemoteReceiveData data) {
 | 
			
		||||
  // Validate header
 | 
			
		||||
  if (!data.expect_item(BALLU_HEADER_MARK, BALLU_HEADER_SPACE)) {
 | 
			
		||||
    ESP_LOGV(TAG, "Header fail");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t remote_state[BALLU_STATE_LENGTH] = {0};
 | 
			
		||||
  // Read all bytes.
 | 
			
		||||
  for (int i = 0; i < BALLU_STATE_LENGTH; i++) {
 | 
			
		||||
    // Read bit
 | 
			
		||||
    for (int j = 0; j < 8; j++) {
 | 
			
		||||
      if (data.expect_item(BALLU_BIT_MARK, BALLU_ONE_SPACE))
 | 
			
		||||
        remote_state[i] |= 1 << j;
 | 
			
		||||
 | 
			
		||||
      else if (!data.expect_item(BALLU_BIT_MARK, BALLU_ZERO_SPACE)) {
 | 
			
		||||
        ESP_LOGV(TAG, "Byte %d bit %d fail", i, j);
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ESP_LOGVV(TAG, "Byte %d %02X", i, remote_state[i]);
 | 
			
		||||
  }
 | 
			
		||||
  // Validate footer
 | 
			
		||||
  if (!data.expect_mark(BALLU_BIT_MARK)) {
 | 
			
		||||
    ESP_LOGV(TAG, "Footer fail");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t checksum = 0;
 | 
			
		||||
  // Calculate  checksum and compare with signal value.
 | 
			
		||||
  for (uint8_t i = 0; i < BALLU_STATE_LENGTH - 1; i++)
 | 
			
		||||
    checksum += remote_state[i];
 | 
			
		||||
 | 
			
		||||
  if (checksum != remote_state[BALLU_STATE_LENGTH - 1]) {
 | 
			
		||||
    ESP_LOGVV(TAG, "Checksum fail");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Received: %02X %02X %02X %02X   %02X %02X %02X %02X   %02X %02X %02X %02X   %02X", remote_state[0],
 | 
			
		||||
           remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6],
 | 
			
		||||
           remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], remote_state[12]);
 | 
			
		||||
 | 
			
		||||
  // verify header remote code
 | 
			
		||||
  if (remote_state[0] != 0xc3)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  // powr on/off button
 | 
			
		||||
  ESP_LOGV(TAG, "Power: %02X", (remote_state[9] & BALLU_POWER));
 | 
			
		||||
 | 
			
		||||
  if ((remote_state[9] & BALLU_POWER) != BALLU_POWER) {
 | 
			
		||||
    this->mode = climate::CLIMATE_MODE_OFF;
 | 
			
		||||
  } else {
 | 
			
		||||
    auto mode = remote_state[6] & 0xe0;
 | 
			
		||||
    ESP_LOGV(TAG, "Mode: %02X", mode);
 | 
			
		||||
    switch (mode) {
 | 
			
		||||
      case BALLU_HEAT:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_HEAT;
 | 
			
		||||
        break;
 | 
			
		||||
      case BALLU_COOL:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_COOL;
 | 
			
		||||
        break;
 | 
			
		||||
      case BALLU_DRY:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_DRY;
 | 
			
		||||
        break;
 | 
			
		||||
      case BALLU_FAN:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_FAN_ONLY;
 | 
			
		||||
        break;
 | 
			
		||||
      case BALLU_AUTO:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_AUTO;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Set received temp
 | 
			
		||||
  int temp = remote_state[1] & 0xf8;
 | 
			
		||||
  ESP_LOGVV(TAG, "Temperature Raw: %02X", temp);
 | 
			
		||||
  temp = ((uint8_t) temp >> 3) + 8;
 | 
			
		||||
  ESP_LOGVV(TAG, "Temperature Climate: %u", temp);
 | 
			
		||||
  this->target_temperature = temp;
 | 
			
		||||
 | 
			
		||||
  // Set received fan speed
 | 
			
		||||
  auto fan = remote_state[4] & 0xe0;
 | 
			
		||||
  ESP_LOGVV(TAG, "Fan: %02X", fan);
 | 
			
		||||
  switch (fan) {
 | 
			
		||||
    case BALLU_FAN_HIGH:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_HIGH;
 | 
			
		||||
      break;
 | 
			
		||||
    case BALLU_FAN_MED:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
 | 
			
		||||
      break;
 | 
			
		||||
    case BALLU_FAN_LOW:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_LOW;
 | 
			
		||||
      break;
 | 
			
		||||
    case BALLU_FAN_AUTO:
 | 
			
		||||
    default:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Set received swing status
 | 
			
		||||
  ESP_LOGVV(TAG, "Swing status: %02X %02X", remote_state[1] & BALLU_SWING_VER, remote_state[2] & BALLU_SWING_HOR);
 | 
			
		||||
  if (((remote_state[1] & BALLU_SWING_VER) != BALLU_SWING_VER) &&
 | 
			
		||||
      ((remote_state[2] & BALLU_SWING_HOR) != BALLU_SWING_HOR)) {
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_BOTH;
 | 
			
		||||
  } else if ((remote_state[1] & BALLU_SWING_VER) != BALLU_SWING_VER) {
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
 | 
			
		||||
  } else if ((remote_state[2] & BALLU_SWING_HOR) != BALLU_SWING_HOR) {
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_OFF;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ballu
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										31
									
								
								esphome/components/ballu/ballu.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								esphome/components/ballu/ballu.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/climate_ir/climate_ir.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ballu {
 | 
			
		||||
 | 
			
		||||
// Support for Ballu air conditioners with YKR-K/002E remote
 | 
			
		||||
 | 
			
		||||
// Temperature
 | 
			
		||||
const float YKR_K_002E_TEMP_MIN = 16.0;
 | 
			
		||||
const float YKR_K_002E_TEMP_MAX = 32.0;
 | 
			
		||||
 | 
			
		||||
class BalluClimate : public climate_ir::ClimateIR {
 | 
			
		||||
 public:
 | 
			
		||||
  BalluClimate()
 | 
			
		||||
      : climate_ir::ClimateIR(YKR_K_002E_TEMP_MIN, YKR_K_002E_TEMP_MAX, 1.0f, true, true,
 | 
			
		||||
                              {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
 | 
			
		||||
                               climate::CLIMATE_FAN_HIGH},
 | 
			
		||||
                              {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
 | 
			
		||||
                               climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  /// Transmit via IR the state of this climate controller.
 | 
			
		||||
  void transmit_state() override;
 | 
			
		||||
  /// Handle received IR Buffer
 | 
			
		||||
  bool on_receive(remote_base::RemoteReceiveData data) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ballu
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										21
									
								
								esphome/components/ballu/climate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								esphome/components/ballu/climate.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import climate_ir
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["climate_ir"]
 | 
			
		||||
CODEOWNERS = ["@bazuchan"]
 | 
			
		||||
 | 
			
		||||
ballu_ns = cg.esphome_ns.namespace("ballu")
 | 
			
		||||
BalluClimate = ballu_ns.class_("BalluClimate", climate_ir.ClimateIR)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(BalluClimate),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await climate_ir.register_climate_ir(var, config)
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bang_bang {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "bang_bang.climate";
 | 
			
		||||
static const char *const TAG = "bang_bang.climate";
 | 
			
		||||
 | 
			
		||||
void BangBangClimate::setup() {
 | 
			
		||||
  this->sensor_->add_on_state_callback([this](float state) {
 | 
			
		||||
@@ -21,7 +21,12 @@ void BangBangClimate::setup() {
 | 
			
		||||
    restore->to_call(this).perform();
 | 
			
		||||
  } else {
 | 
			
		||||
    // restore from defaults, change_away handles those for us
 | 
			
		||||
    this->mode = climate::CLIMATE_MODE_AUTO;
 | 
			
		||||
    if (supports_cool_ && supports_heat_)
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_HEAT_COOL;
 | 
			
		||||
    else if (supports_cool_)
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_COOL;
 | 
			
		||||
    else if (supports_heat_)
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_HEAT;
 | 
			
		||||
    this->change_away_(false);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -32,8 +37,8 @@ void BangBangClimate::control(const climate::ClimateCall &call) {
 | 
			
		||||
    this->target_temperature_low = *call.get_target_temperature_low();
 | 
			
		||||
  if (call.get_target_temperature_high().has_value())
 | 
			
		||||
    this->target_temperature_high = *call.get_target_temperature_high();
 | 
			
		||||
  if (call.get_away().has_value())
 | 
			
		||||
    this->change_away_(*call.get_away());
 | 
			
		||||
  if (call.get_preset().has_value())
 | 
			
		||||
    this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY);
 | 
			
		||||
 | 
			
		||||
  this->compute_state_();
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
@@ -41,21 +46,27 @@ void BangBangClimate::control(const climate::ClimateCall &call) {
 | 
			
		||||
climate::ClimateTraits BangBangClimate::traits() {
 | 
			
		||||
  auto traits = climate::ClimateTraits();
 | 
			
		||||
  traits.set_supports_current_temperature(true);
 | 
			
		||||
  traits.set_supports_auto_mode(true);
 | 
			
		||||
  traits.set_supports_cool_mode(this->supports_cool_);
 | 
			
		||||
  traits.set_supports_heat_mode(this->supports_heat_);
 | 
			
		||||
  traits.set_supported_modes({
 | 
			
		||||
      climate::CLIMATE_MODE_OFF,
 | 
			
		||||
  });
 | 
			
		||||
  if (supports_cool_)
 | 
			
		||||
    traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
 | 
			
		||||
  if (supports_heat_)
 | 
			
		||||
    traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
 | 
			
		||||
  if (supports_cool_ && supports_heat_)
 | 
			
		||||
    traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
 | 
			
		||||
  traits.set_supports_two_point_target_temperature(true);
 | 
			
		||||
  traits.set_supports_away(this->supports_away_);
 | 
			
		||||
  if (supports_away_)
 | 
			
		||||
    traits.set_supported_presets({
 | 
			
		||||
        climate::CLIMATE_PRESET_HOME,
 | 
			
		||||
        climate::CLIMATE_PRESET_AWAY,
 | 
			
		||||
    });
 | 
			
		||||
  traits.set_supports_action(true);
 | 
			
		||||
  return traits;
 | 
			
		||||
}
 | 
			
		||||
void BangBangClimate::compute_state_() {
 | 
			
		||||
  if (this->mode != climate::CLIMATE_MODE_AUTO) {
 | 
			
		||||
    // in non-auto mode, switch directly to appropriate action
 | 
			
		||||
    //  - HEAT mode -> HEATING action
 | 
			
		||||
    //  - COOL mode -> COOLING action
 | 
			
		||||
    //  - OFF mode -> OFF action (not IDLE!)
 | 
			
		||||
    this->switch_to_action_(static_cast<climate::ClimateAction>(this->mode));
 | 
			
		||||
  if (this->mode == climate::CLIMATE_MODE_OFF) {
 | 
			
		||||
    this->switch_to_action_(climate::CLIMATE_ACTION_OFF);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) {
 | 
			
		||||
@@ -140,7 +151,7 @@ void BangBangClimate::change_away_(bool away) {
 | 
			
		||||
    this->target_temperature_low = this->away_config_.default_temperature_low;
 | 
			
		||||
    this->target_temperature_high = this->away_config_.default_temperature_high;
 | 
			
		||||
  }
 | 
			
		||||
  this->away = away;
 | 
			
		||||
  this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME;
 | 
			
		||||
}
 | 
			
		||||
void BangBangClimate::set_normal_config(const BangBangClimateTargetTempConfig &normal_config) {
 | 
			
		||||
  this->normal_config_ = normal_config;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bh1750 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "bh1750.sensor";
 | 
			
		||||
static const char *const TAG = "bh1750.sensor";
 | 
			
		||||
 | 
			
		||||
static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001;
 | 
			
		||||
static const uint8_t BH1750_COMMAND_MT_REG_HI = 0b01000000;  // last 3 bits
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace binary {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "binary.fan";
 | 
			
		||||
static const char *const TAG = "binary.fan";
 | 
			
		||||
 | 
			
		||||
void binary::BinaryFan::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Fan '%s':", this->fan_->get_name().c_str());
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_STATE,
 | 
			
		||||
    CONF_TIMING,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_FOR,
 | 
			
		||||
    CONF_NAME,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
@@ -372,11 +371,6 @@ BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_INVERTED): cv.invalid(
 | 
			
		||||
            "The inverted binary_sensor property has been replaced by the "
 | 
			
		||||
            "new 'invert' binary  sensor filter. Please see "
 | 
			
		||||
            "https://esphome.io/components/binary_sensor/index.html."
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -455,10 +449,6 @@ async def new_binary_sensor(config):
 | 
			
		||||
BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(BinarySensor),
 | 
			
		||||
        cv.Optional(CONF_FOR): cv.invalid(
 | 
			
		||||
            "This option has been removed in 1.13, please use the "
 | 
			
		||||
            "'for' condition instead."
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace binary_sensor {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "binary_sensor.automation";
 | 
			
		||||
static const char *const TAG = "binary_sensor.automation";
 | 
			
		||||
 | 
			
		||||
void binary_sensor::MultiClickTrigger::on_state_(bool state) {
 | 
			
		||||
  // Handle duplicate events
 | 
			
		||||
@@ -80,6 +80,10 @@ void binary_sensor::MultiClickTrigger::schedule_cooldown_() {
 | 
			
		||||
  this->cancel_timeout("is_not_valid");
 | 
			
		||||
}
 | 
			
		||||
void binary_sensor::MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
 | 
			
		||||
  if (min_length == 0) {
 | 
			
		||||
    this->is_valid_ = true;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->is_valid_ = false;
 | 
			
		||||
  this->set_timeout("is_valid", min_length, [this]() {
 | 
			
		||||
    ESP_LOGV(TAG, "Multi Click: You can now %s the button.", this->parent_->state ? "RELEASE" : "PRESS");
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
@@ -87,8 +89,8 @@ class DoubleClickTrigger : public Trigger<> {
 | 
			
		||||
 | 
			
		||||
class MultiClickTrigger : public Trigger<>, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MultiClickTrigger(BinarySensor *parent, const std::vector<MultiClickTriggerEvent> &timing)
 | 
			
		||||
      : parent_(parent), timing_(timing) {}
 | 
			
		||||
  explicit MultiClickTrigger(BinarySensor *parent, std::vector<MultiClickTriggerEvent> timing)
 | 
			
		||||
      : parent_(parent), timing_(std::move(timing)) {}
 | 
			
		||||
 | 
			
		||||
  void setup() override {
 | 
			
		||||
    this->last_state_ = this->parent_->state;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ namespace esphome {
 | 
			
		||||
 | 
			
		||||
namespace binary_sensor {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "binary_sensor";
 | 
			
		||||
static const char *const TAG = "binary_sensor";
 | 
			
		||||
 | 
			
		||||
void BinarySensor::add_on_state_callback(std::function<void(bool)> &&callback) {
 | 
			
		||||
  this->state_callback_.add(std::move(callback));
 | 
			
		||||
@@ -61,7 +61,7 @@ void BinarySensor::add_filter(Filter *filter) {
 | 
			
		||||
    last_filter->next_ = filter;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BinarySensor::add_filters(std::vector<Filter *> filters) {
 | 
			
		||||
void BinarySensor::add_filters(const std::vector<Filter *> &filters) {
 | 
			
		||||
  for (Filter *filter : filters) {
 | 
			
		||||
    this->add_filter(filter);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,10 @@ namespace esphome {
 | 
			
		||||
namespace binary_sensor {
 | 
			
		||||
 | 
			
		||||
#define LOG_BINARY_SENSOR(prefix, type, obj) \
 | 
			
		||||
  if (obj != nullptr) { \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, obj->get_name().c_str()); \
 | 
			
		||||
    if (!obj->get_device_class().empty()) { \
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "%s  Device Class: '%s'", prefix, obj->get_device_class().c_str()); \
 | 
			
		||||
  if ((obj) != nullptr) { \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
 | 
			
		||||
    if (!(obj)->get_device_class().empty()) { \
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "%s  Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
 | 
			
		||||
    } \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -60,7 +60,7 @@ class BinarySensor : public Nameable {
 | 
			
		||||
  std::string get_device_class();
 | 
			
		||||
 | 
			
		||||
  void add_filter(Filter *filter);
 | 
			
		||||
  void add_filters(std::vector<Filter *> filters);
 | 
			
		||||
  void add_filters(const std::vector<Filter *> &filters);
 | 
			
		||||
 | 
			
		||||
  // ========== INTERNAL METHODS ==========
 | 
			
		||||
  // (In most use cases you won't need these)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,13 @@
 | 
			
		||||
#include "filter.h"
 | 
			
		||||
 | 
			
		||||
#include "binary_sensor.h"
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
 | 
			
		||||
namespace binary_sensor {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "sensor.filter";
 | 
			
		||||
static const char *const TAG = "sensor.filter";
 | 
			
		||||
 | 
			
		||||
void Filter::output(bool value, bool is_initial) {
 | 
			
		||||
  if (!this->dedup_.next(value))
 | 
			
		||||
@@ -64,7 +66,7 @@ float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARD
 | 
			
		||||
 | 
			
		||||
optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
 | 
			
		||||
 | 
			
		||||
AutorepeatFilter::AutorepeatFilter(const std::vector<AutorepeatFilterTiming> &timings) : timings_(timings) {}
 | 
			
		||||
AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(timings)) {}
 | 
			
		||||
 | 
			
		||||
optional<bool> AutorepeatFilter::new_value(bool value, bool is_initial) {
 | 
			
		||||
  if (value) {
 | 
			
		||||
@@ -108,7 +110,7 @@ void AutorepeatFilter::next_value_(bool val) {
 | 
			
		||||
 | 
			
		||||
float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
 | 
			
		||||
 | 
			
		||||
LambdaFilter::LambdaFilter(const std::function<optional<bool>(bool)> &f) : f_(f) {}
 | 
			
		||||
LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move(f)) {}
 | 
			
		||||
 | 
			
		||||
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,7 @@ struct AutorepeatFilterTiming {
 | 
			
		||||
 | 
			
		||||
class AutorepeatFilter : public Filter, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit AutorepeatFilter(const std::vector<AutorepeatFilterTiming> &timings);
 | 
			
		||||
  explicit AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings);
 | 
			
		||||
 | 
			
		||||
  optional<bool> new_value(bool value, bool is_initial) override;
 | 
			
		||||
 | 
			
		||||
@@ -95,7 +95,7 @@ class AutorepeatFilter : public Filter, public Component {
 | 
			
		||||
 | 
			
		||||
class LambdaFilter : public Filter {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit LambdaFilter(const std::function<optional<bool>(bool)> &f);
 | 
			
		||||
  explicit LambdaFilter(std::function<optional<bool>(bool)> f);
 | 
			
		||||
 | 
			
		||||
  optional<bool> new_value(bool value, bool is_initial) override;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace binary_sensor_map {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "binary_sensor_map";
 | 
			
		||||
static const char *const TAG = "binary_sensor_map";
 | 
			
		||||
 | 
			
		||||
void BinarySensorMap::dump_config() { LOG_SENSOR("  ", "binary_sensor_map", this); }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_client {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "ble_client";
 | 
			
		||||
static const char *const TAG = "ble_client";
 | 
			
		||||
 | 
			
		||||
void BLEClient::setup() {
 | 
			
		||||
  auto ret = esp_ble_gattc_app_register(this->app_id);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_client {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "ble_sensor";
 | 
			
		||||
static const char *const TAG = "ble_sensor";
 | 
			
		||||
 | 
			
		||||
uint32_t BLESensor::hash_base() { return 343459825UL; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_client {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "ble_switch";
 | 
			
		||||
static const char *const TAG = "ble_switch";
 | 
			
		||||
 | 
			
		||||
void BLEClientSwitch::write_state(bool state) {
 | 
			
		||||
  this->parent_->set_enabled(state);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_presence {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "ble_presence";
 | 
			
		||||
static const char *const TAG = "ble_presence";
 | 
			
		||||
 | 
			
		||||
void BLEPresenceDevice::dump_config() { LOG_BINARY_SENSOR("", "BLE Presence", this); }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_rssi {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "ble_rssi";
 | 
			
		||||
static const char *const TAG = "ble_rssi";
 | 
			
		||||
 | 
			
		||||
void BLERSSISensor::dump_config() { LOG_SENSOR("", "BLE RSSI Sensor", this); }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_scanner {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "ble_scanner";
 | 
			
		||||
static const char *const TAG = "ble_scanner";
 | 
			
		||||
 | 
			
		||||
void BLEScanner::dump_config() { LOG_TEXT_SENSOR("", "BLE Scanner", this); }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bme280 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "bme280.sensor";
 | 
			
		||||
static const char *const TAG = "bme280.sensor";
 | 
			
		||||
 | 
			
		||||
static const uint8_t BME280_REGISTER_DIG_T1 = 0x88;
 | 
			
		||||
static const uint8_t BME280_REGISTER_DIG_T2 = 0x8A;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bme680 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "bme680.sensor";
 | 
			
		||||
static const char *const TAG = "bme680.sensor";
 | 
			
		||||
 | 
			
		||||
static const uint8_t BME680_REGISTER_COEFF1 = 0x89;
 | 
			
		||||
static const uint8_t BME680_REGISTER_COEFF2 = 0xE1;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bme680_bsec {
 | 
			
		||||
#ifdef USE_BSEC
 | 
			
		||||
static const char *TAG = "bme680_bsec.sensor";
 | 
			
		||||
static const char *const TAG = "bme680_bsec.sensor";
 | 
			
		||||
 | 
			
		||||
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bmp085 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "bmp085.sensor";
 | 
			
		||||
static const char *const TAG = "bmp085.sensor";
 | 
			
		||||
 | 
			
		||||
static const uint8_t BMP085_ADDRESS = 0x77;
 | 
			
		||||
static const uint8_t BMP085_REGISTER_AC1_H = 0xAA;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bmp280 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "bmp280.sensor";
 | 
			
		||||
static const char *const TAG = "bmp280.sensor";
 | 
			
		||||
 | 
			
		||||
static const uint8_t BMP280_REGISTER_STATUS = 0xF3;
 | 
			
		||||
static const uint8_t BMP280_REGISTER_CONTROL = 0xF4;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace canbus {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "canbus";
 | 
			
		||||
static const char *const TAG = "canbus";
 | 
			
		||||
 | 
			
		||||
void Canbus::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up Canbus...");
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace captive_portal {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "captive_portal";
 | 
			
		||||
static const char *const TAG = "captive_portal";
 | 
			
		||||
 | 
			
		||||
void CaptivePortal::handle_index(AsyncWebServerRequest *request) {
 | 
			
		||||
  AsyncResponseStream *stream = request->beginResponseStream("text/html");
 | 
			
		||||
@@ -147,7 +147,7 @@ float CaptivePortal::get_setup_priority() const {
 | 
			
		||||
}
 | 
			
		||||
void CaptivePortal::dump_config() { ESP_LOGCONFIG(TAG, "Captive Portal:"); }
 | 
			
		||||
 | 
			
		||||
CaptivePortal *global_captive_portal = nullptr;
 | 
			
		||||
CaptivePortal *global_captive_portal = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
}  // namespace captive_portal
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ class CaptivePortal : public AsyncWebHandler, public Component {
 | 
			
		||||
  DNSServer *dns_server_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern CaptivePortal *global_captive_portal;
 | 
			
		||||
extern CaptivePortal *global_captive_portal;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
}  // namespace captive_portal
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ccs811 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "ccs811";
 | 
			
		||||
static const char *const TAG = "ccs811";
 | 
			
		||||
 | 
			
		||||
// based on
 | 
			
		||||
//  - https://cdn.sparkfun.com/datasheets/BreakoutBoards/CCS811_Programming_Guide.pdf
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,8 @@ from esphome.const import (
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_PARTS_PER_MILLION,
 | 
			
		||||
    UNIT_PARTS_PER_BILLION,
 | 
			
		||||
    CONF_BASELINE,
 | 
			
		||||
    CONF_ECO2,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    CONF_TVOC,
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
@@ -21,9 +23,6 @@ CCS811Component = ccs811_ns.class_(
 | 
			
		||||
    "CCS811Component", cg.PollingComponent, i2c.I2CDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_ECO2 = "eco2"
 | 
			
		||||
CONF_BASELINE = "baseline"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ ClimateTraits = climate_ns.class_("ClimateTraits")
 | 
			
		||||
ClimateMode = climate_ns.enum("ClimateMode")
 | 
			
		||||
CLIMATE_MODES = {
 | 
			
		||||
    "OFF": ClimateMode.CLIMATE_MODE_OFF,
 | 
			
		||||
    "HEAT_COOL": ClimateMode.CLIMATE_HEAT_COOL,
 | 
			
		||||
    "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL,
 | 
			
		||||
    "COOL": ClimateMode.CLIMATE_MODE_COOL,
 | 
			
		||||
    "HEAT": ClimateMode.CLIMATE_MODE_HEAT,
 | 
			
		||||
    "DRY": ClimateMode.CLIMATE_MODE_DRY,
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,9 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
 | 
			
		||||
    call.set_target_temperature(this->target_temperature_.optional_value(x...));
 | 
			
		||||
    call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...));
 | 
			
		||||
    call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...));
 | 
			
		||||
    call.set_away(this->away_.optional_value(x...));
 | 
			
		||||
    if (away_.has_value()) {
 | 
			
		||||
      call.set_preset(away_.value(x...) ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME);
 | 
			
		||||
    }
 | 
			
		||||
    call.set_fan_mode(this->fan_mode_.optional_value(x...));
 | 
			
		||||
    call.set_fan_mode(this->custom_fan_mode_.optional_value(x...));
 | 
			
		||||
    call.set_preset(this->preset_.optional_value(x...));
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace climate {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "climate";
 | 
			
		||||
static const char *const TAG = "climate";
 | 
			
		||||
 | 
			
		||||
void ClimateCall::perform() {
 | 
			
		||||
  ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
 | 
			
		||||
@@ -43,9 +43,6 @@ void ClimateCall::perform() {
 | 
			
		||||
  if (this->target_temperature_high_.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, "  Target Temperature High: %.2f", *this->target_temperature_high_);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->away_.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, "  Away Mode: %s", ONOFF(*this->away_));
 | 
			
		||||
  }
 | 
			
		||||
  this->parent_->control(*this);
 | 
			
		||||
}
 | 
			
		||||
void ClimateCall::validate_() {
 | 
			
		||||
@@ -125,12 +122,6 @@ void ClimateCall::validate_() {
 | 
			
		||||
      this->target_temperature_high_.reset();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (this->away_.has_value()) {
 | 
			
		||||
    if (!traits.get_supports_away()) {
 | 
			
		||||
      ESP_LOGW(TAG, "  Cannot set away mode for this device!");
 | 
			
		||||
      this->away_.reset();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
ClimateCall &ClimateCall::set_mode(ClimateMode mode) {
 | 
			
		||||
  this->mode_ = mode;
 | 
			
		||||
@@ -181,8 +172,7 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
 | 
			
		||||
  } else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) {
 | 
			
		||||
    this->set_fan_mode(CLIMATE_FAN_DIFFUSE);
 | 
			
		||||
  } else {
 | 
			
		||||
    auto custom_fan_modes = this->parent_->get_traits().get_supported_custom_fan_modes();
 | 
			
		||||
    if (std::find(custom_fan_modes.begin(), custom_fan_modes.end(), fan_mode) != custom_fan_modes.end()) {
 | 
			
		||||
    if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) {
 | 
			
		||||
      this->custom_fan_mode_ = fan_mode;
 | 
			
		||||
      this->fan_mode_.reset();
 | 
			
		||||
    } else {
 | 
			
		||||
@@ -218,8 +208,7 @@ ClimateCall &ClimateCall::set_preset(const std::string &preset) {
 | 
			
		||||
  } else if (str_equals_case_insensitive(preset, "ACTIVITY")) {
 | 
			
		||||
    this->set_preset(CLIMATE_PRESET_ACTIVITY);
 | 
			
		||||
  } else {
 | 
			
		||||
    auto custom_presets = this->parent_->get_traits().get_supported_custom_presets();
 | 
			
		||||
    if (std::find(custom_presets.begin(), custom_presets.end(), preset) != custom_presets.end()) {
 | 
			
		||||
    if (this->parent_->get_traits().supports_custom_preset(preset)) {
 | 
			
		||||
      this->custom_preset_ = preset;
 | 
			
		||||
      this->preset_.reset();
 | 
			
		||||
    } else {
 | 
			
		||||
@@ -269,18 +258,23 @@ const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_;
 | 
			
		||||
const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; }
 | 
			
		||||
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
 | 
			
		||||
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
 | 
			
		||||
const optional<bool> &ClimateCall::get_away() const { return this->away_; }
 | 
			
		||||
optional<bool> ClimateCall::get_away() const {
 | 
			
		||||
  if (!this->preset_.has_value())
 | 
			
		||||
    return {};
 | 
			
		||||
  return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY;
 | 
			
		||||
}
 | 
			
		||||
const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
 | 
			
		||||
const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; }
 | 
			
		||||
const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; }
 | 
			
		||||
const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; }
 | 
			
		||||
const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; }
 | 
			
		||||
ClimateCall &ClimateCall::set_away(bool away) {
 | 
			
		||||
  this->away_ = away;
 | 
			
		||||
  this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
ClimateCall &ClimateCall::set_away(optional<bool> away) {
 | 
			
		||||
  this->away_ = away;
 | 
			
		||||
  if (away.has_value())
 | 
			
		||||
    this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) {
 | 
			
		||||
@@ -338,20 +332,17 @@ void Climate::save_state_() {
 | 
			
		||||
  } else {
 | 
			
		||||
    state.target_temperature = this->target_temperature;
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_away()) {
 | 
			
		||||
    state.away = this->away;
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_fan_modes() && fan_mode.has_value()) {
 | 
			
		||||
    state.uses_custom_fan_mode = false;
 | 
			
		||||
    state.fan_mode = this->fan_mode.value();
 | 
			
		||||
  }
 | 
			
		||||
  if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) {
 | 
			
		||||
    state.uses_custom_fan_mode = true;
 | 
			
		||||
    auto &custom_fan_modes = traits.get_supported_custom_fan_modes();
 | 
			
		||||
    auto it = std::find(custom_fan_modes.begin(), custom_fan_modes.end(), this->custom_fan_mode.value());
 | 
			
		||||
    // only set custom fan mode if value exists, otherwise leave it as is
 | 
			
		||||
    if (it != custom_fan_modes.cend()) {
 | 
			
		||||
      state.custom_fan_mode = std::distance(custom_fan_modes.begin(), it);
 | 
			
		||||
    const auto &supported = traits.get_supported_custom_fan_modes();
 | 
			
		||||
    std::vector<std::string> vec{supported.begin(), supported.end()};
 | 
			
		||||
    auto it = std::find(vec.begin(), vec.end(), custom_fan_mode);
 | 
			
		||||
    if (it != vec.end()) {
 | 
			
		||||
      state.custom_fan_mode = std::distance(vec.begin(), it);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_presets() && preset.has_value()) {
 | 
			
		||||
@@ -360,11 +351,12 @@ void Climate::save_state_() {
 | 
			
		||||
  }
 | 
			
		||||
  if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) {
 | 
			
		||||
    state.uses_custom_preset = true;
 | 
			
		||||
    auto custom_presets = traits.get_supported_custom_presets();
 | 
			
		||||
    auto it = std::find(custom_presets.begin(), custom_presets.end(), this->custom_preset.value());
 | 
			
		||||
    const auto &supported = traits.get_supported_custom_presets();
 | 
			
		||||
    std::vector<std::string> vec{supported.begin(), supported.end()};
 | 
			
		||||
    auto it = std::find(vec.begin(), vec.end(), custom_preset);
 | 
			
		||||
    // only set custom preset if value exists, otherwise leave it as is
 | 
			
		||||
    if (it != custom_presets.cend()) {
 | 
			
		||||
      state.custom_preset = std::distance(custom_presets.begin(), it);
 | 
			
		||||
    if (it != vec.cend()) {
 | 
			
		||||
      state.custom_preset = std::distance(vec.begin(), it);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_swing_modes()) {
 | 
			
		||||
@@ -405,9 +397,6 @@ void Climate::publish_state() {
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGD(TAG, "  Target Temperature: %.2f°C", this->target_temperature);
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_away()) {
 | 
			
		||||
    ESP_LOGD(TAG, "  Away: %s", ONOFF(this->away));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Send state to frontend
 | 
			
		||||
  this->state_callback_.call();
 | 
			
		||||
@@ -453,9 +442,6 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
 | 
			
		||||
  } else {
 | 
			
		||||
    call.set_target_temperature(this->target_temperature);
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_away()) {
 | 
			
		||||
    call.set_away(this->away);
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
 | 
			
		||||
    call.set_fan_mode(this->fan_mode);
 | 
			
		||||
  }
 | 
			
		||||
@@ -476,23 +462,27 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
 | 
			
		||||
  } else {
 | 
			
		||||
    climate->target_temperature = this->target_temperature;
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_away()) {
 | 
			
		||||
    climate->away = this->away;
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) {
 | 
			
		||||
    climate->fan_mode = this->fan_mode;
 | 
			
		||||
  }
 | 
			
		||||
  if (!traits.get_supported_custom_fan_modes().empty() && this->uses_custom_fan_mode) {
 | 
			
		||||
    climate->custom_fan_mode = traits.get_supported_custom_fan_modes()[this->custom_fan_mode];
 | 
			
		||||
    // std::set has consistent order (lexicographic for strings), so this is ok
 | 
			
		||||
    const auto &modes = traits.get_supported_custom_fan_modes();
 | 
			
		||||
    std::vector<std::string> modes_vec{modes.begin(), modes.end()};
 | 
			
		||||
    if (custom_fan_mode < modes_vec.size()) {
 | 
			
		||||
      climate->custom_fan_mode = modes_vec[this->custom_fan_mode];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_presets() && !this->uses_custom_preset) {
 | 
			
		||||
    climate->preset = this->preset;
 | 
			
		||||
  }
 | 
			
		||||
  if (!traits.get_supported_custom_presets().empty() && this->uses_custom_preset) {
 | 
			
		||||
    climate->custom_preset = traits.get_supported_custom_presets()[this->custom_preset];
 | 
			
		||||
  }
 | 
			
		||||
  if (!traits.get_supported_custom_presets().empty() && uses_custom_preset) {
 | 
			
		||||
    climate->custom_preset = traits.get_supported_custom_presets()[this->preset];
 | 
			
		||||
    // std::set has consistent order (lexicographic for strings), so this is ok
 | 
			
		||||
    const auto &presets = traits.get_supported_custom_presets();
 | 
			
		||||
    std::vector<std::string> presets_vec{presets.begin(), presets.end()};
 | 
			
		||||
    if (custom_preset < presets_vec.size()) {
 | 
			
		||||
      climate->custom_preset = presets_vec[this->custom_preset];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_swing_modes()) {
 | 
			
		||||
    climate->swing_mode = this->swing_mode;
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,8 @@ namespace esphome {
 | 
			
		||||
namespace climate {
 | 
			
		||||
 | 
			
		||||
#define LOG_CLIMATE(prefix, type, obj) \
 | 
			
		||||
  if (obj != nullptr) { \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, obj->get_name().c_str()); \
 | 
			
		||||
  if ((obj) != nullptr) { \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
class Climate;
 | 
			
		||||
@@ -63,7 +63,9 @@ class ClimateCall {
 | 
			
		||||
   * For climate devices with two point target temperature control
 | 
			
		||||
   */
 | 
			
		||||
  ClimateCall &set_target_temperature_high(optional<float> target_temperature_high);
 | 
			
		||||
  ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead")
 | 
			
		||||
  ClimateCall &set_away(bool away);
 | 
			
		||||
  ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead")
 | 
			
		||||
  ClimateCall &set_away(optional<bool> away);
 | 
			
		||||
  /// Set the fan mode of the climate device.
 | 
			
		||||
  ClimateCall &set_fan_mode(ClimateFanMode fan_mode);
 | 
			
		||||
@@ -94,7 +96,8 @@ class ClimateCall {
 | 
			
		||||
  const optional<float> &get_target_temperature() const;
 | 
			
		||||
  const optional<float> &get_target_temperature_low() const;
 | 
			
		||||
  const optional<float> &get_target_temperature_high() const;
 | 
			
		||||
  const optional<bool> &get_away() const;
 | 
			
		||||
  ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead")
 | 
			
		||||
  optional<bool> get_away() const;
 | 
			
		||||
  const optional<ClimateFanMode> &get_fan_mode() const;
 | 
			
		||||
  const optional<ClimateSwingMode> &get_swing_mode() const;
 | 
			
		||||
  const optional<std::string> &get_custom_fan_mode() const;
 | 
			
		||||
@@ -109,7 +112,6 @@ class ClimateCall {
 | 
			
		||||
  optional<float> target_temperature_;
 | 
			
		||||
  optional<float> target_temperature_low_;
 | 
			
		||||
  optional<float> target_temperature_high_;
 | 
			
		||||
  optional<bool> away_;
 | 
			
		||||
  optional<ClimateFanMode> fan_mode_;
 | 
			
		||||
  optional<ClimateSwingMode> swing_mode_;
 | 
			
		||||
  optional<std::string> custom_fan_mode_;
 | 
			
		||||
@@ -120,7 +122,6 @@ class ClimateCall {
 | 
			
		||||
/// Struct used to save the state of the climate device in restore memory.
 | 
			
		||||
struct ClimateDeviceRestoreState {
 | 
			
		||||
  ClimateMode mode;
 | 
			
		||||
  bool away;
 | 
			
		||||
  bool uses_custom_fan_mode{false};
 | 
			
		||||
  union {
 | 
			
		||||
    ClimateFanMode fan_mode;
 | 
			
		||||
@@ -159,7 +160,7 @@ struct ClimateDeviceRestoreState {
 | 
			
		||||
 *
 | 
			
		||||
 * The entire state of the climate device is encoded in public properties of the base class (current_temperature,
 | 
			
		||||
 * mode etc). These are read-only for the user and rw for integrations. The reason these are public
 | 
			
		||||
 * is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_AUTO) ...`
 | 
			
		||||
 * is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...`
 | 
			
		||||
 */
 | 
			
		||||
class Climate : public Nameable {
 | 
			
		||||
 public:
 | 
			
		||||
@@ -191,6 +192,7 @@ class Climate : public Nameable {
 | 
			
		||||
   * Away allows climate devices to have two different target temperature configs:
 | 
			
		||||
   * one for normal mode and one for away mode.
 | 
			
		||||
   */
 | 
			
		||||
  ESPDEPRECATED("away is deprecated, use preset instead")
 | 
			
		||||
  bool away{false};
 | 
			
		||||
 | 
			
		||||
  /// The active fan mode of the climate device.
 | 
			
		||||
 
 | 
			
		||||
@@ -84,6 +84,10 @@ const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) {
 | 
			
		||||
 | 
			
		||||
const char *climate_preset_to_string(ClimatePreset preset) {
 | 
			
		||||
  switch (preset) {
 | 
			
		||||
    case climate::CLIMATE_PRESET_NONE:
 | 
			
		||||
      return "NONE";
 | 
			
		||||
    case climate::CLIMATE_PRESET_HOME:
 | 
			
		||||
      return "HOME";
 | 
			
		||||
    case climate::CLIMATE_PRESET_ECO:
 | 
			
		||||
      return "ECO";
 | 
			
		||||
    case climate::CLIMATE_PRESET_AWAY:
 | 
			
		||||
@@ -92,8 +96,6 @@ const char *climate_preset_to_string(ClimatePreset preset) {
 | 
			
		||||
      return "BOOST";
 | 
			
		||||
    case climate::CLIMATE_PRESET_COMFORT:
 | 
			
		||||
      return "COMFORT";
 | 
			
		||||
    case climate::CLIMATE_PRESET_HOME:
 | 
			
		||||
      return "HOME";
 | 
			
		||||
    case climate::CLIMATE_PRESET_SLEEP:
 | 
			
		||||
      return "SLEEP";
 | 
			
		||||
    case climate::CLIMATE_PRESET_ACTIVITY:
 | 
			
		||||
 
 | 
			
		||||
@@ -7,19 +7,22 @@ namespace climate {
 | 
			
		||||
 | 
			
		||||
/// Enum for all modes a climate device can be in.
 | 
			
		||||
enum ClimateMode : uint8_t {
 | 
			
		||||
  /// The climate device is off (not in auto, heat or cool mode)
 | 
			
		||||
  /// The climate device is off
 | 
			
		||||
  CLIMATE_MODE_OFF = 0,
 | 
			
		||||
  /// The climate device is set to automatically change the heating/cooling cycle
 | 
			
		||||
  /// The climate device is set to heat/cool to reach the target temperature.
 | 
			
		||||
  CLIMATE_MODE_HEAT_COOL = 1,
 | 
			
		||||
  /// The climate device is manually set to cool mode (not in auto mode!)
 | 
			
		||||
  /// The climate device is set to cool to reach the target temperature
 | 
			
		||||
  CLIMATE_MODE_COOL = 2,
 | 
			
		||||
  /// The climate device is manually set to heat mode (not in auto mode!)
 | 
			
		||||
  /// The climate device is set to heat to reach the target temperature
 | 
			
		||||
  CLIMATE_MODE_HEAT = 3,
 | 
			
		||||
  /// The climate device is manually set to fan only mode
 | 
			
		||||
  /// The climate device only has the fan enabled, no heating or cooling is taking place
 | 
			
		||||
  CLIMATE_MODE_FAN_ONLY = 4,
 | 
			
		||||
  /// The climate device is manually set to dry mode
 | 
			
		||||
  /// The climate device is set to dry/humidity mode
 | 
			
		||||
  CLIMATE_MODE_DRY = 5,
 | 
			
		||||
  /// The climate device is manually set to heat-cool mode
 | 
			
		||||
  /** The climate device is adjusting the temperatre dynamically.
 | 
			
		||||
   * For example, the target temperature can be adjusted based on a schedule, or learned behavior.
 | 
			
		||||
   * The target temperature can't be adjusted when in this mode.
 | 
			
		||||
   */
 | 
			
		||||
  CLIMATE_MODE_AUTO = 6
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -27,19 +30,18 @@ enum ClimateMode : uint8_t {
 | 
			
		||||
enum ClimateAction : uint8_t {
 | 
			
		||||
  /// The climate device is off (inactive or no power)
 | 
			
		||||
  CLIMATE_ACTION_OFF = 0,
 | 
			
		||||
  /// The climate device is actively cooling (usually in cool or auto mode)
 | 
			
		||||
  /// The climate device is actively cooling
 | 
			
		||||
  CLIMATE_ACTION_COOLING = 2,
 | 
			
		||||
  /// The climate device is actively heating (usually in heat or auto mode)
 | 
			
		||||
  /// The climate device is actively heating
 | 
			
		||||
  CLIMATE_ACTION_HEATING = 3,
 | 
			
		||||
  /// The climate device is idle (monitoring climate but no action needed)
 | 
			
		||||
  CLIMATE_ACTION_IDLE = 4,
 | 
			
		||||
  /// The climate device is drying (either mode DRY or AUTO)
 | 
			
		||||
  /// The climate device is drying
 | 
			
		||||
  CLIMATE_ACTION_DRYING = 5,
 | 
			
		||||
  /// The climate device is in fan only mode (either mode FAN_ONLY or AUTO)
 | 
			
		||||
  /// The climate device is in fan only mode
 | 
			
		||||
  CLIMATE_ACTION_FAN = 6,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Enum for all modes a climate fan can be in
 | 
			
		||||
enum ClimateFanMode : uint8_t {
 | 
			
		||||
  /// The fan mode is set to On
 | 
			
		||||
  CLIMATE_FAN_ON = 0,
 | 
			
		||||
@@ -75,20 +77,22 @@ enum ClimateSwingMode : uint8_t {
 | 
			
		||||
 | 
			
		||||
/// Enum for all modes a climate swing can be in
 | 
			
		||||
enum ClimatePreset : uint8_t {
 | 
			
		||||
  /// Preset is set to ECO
 | 
			
		||||
  CLIMATE_PRESET_ECO = 0,
 | 
			
		||||
  /// Preset is set to AWAY
 | 
			
		||||
  CLIMATE_PRESET_AWAY = 1,
 | 
			
		||||
  /// Preset is set to BOOST
 | 
			
		||||
  CLIMATE_PRESET_BOOST = 2,
 | 
			
		||||
  /// Preset is set to COMFORT
 | 
			
		||||
  CLIMATE_PRESET_COMFORT = 3,
 | 
			
		||||
  /// Preset is set to HOME
 | 
			
		||||
  CLIMATE_PRESET_HOME = 4,
 | 
			
		||||
  /// Preset is set to SLEEP
 | 
			
		||||
  CLIMATE_PRESET_SLEEP = 5,
 | 
			
		||||
  /// Preset is set to ACTIVITY
 | 
			
		||||
  CLIMATE_PRESET_ACTIVITY = 6,
 | 
			
		||||
  /// No preset is active
 | 
			
		||||
  CLIMATE_PRESET_NONE = 0,
 | 
			
		||||
  /// Device is in home preset
 | 
			
		||||
  CLIMATE_PRESET_HOME = 1,
 | 
			
		||||
  /// Device is in away preset
 | 
			
		||||
  CLIMATE_PRESET_AWAY = 2,
 | 
			
		||||
  /// Device is in boost preset
 | 
			
		||||
  CLIMATE_PRESET_BOOST = 3,
 | 
			
		||||
  /// Device is in comfort preset
 | 
			
		||||
  CLIMATE_PRESET_COMFORT = 4,
 | 
			
		||||
  /// Device is running an energy-saving preset
 | 
			
		||||
  CLIMATE_PRESET_ECO = 5,
 | 
			
		||||
  /// Device is prepared for sleep
 | 
			
		||||
  CLIMATE_PRESET_SLEEP = 6,
 | 
			
		||||
  /// Device is reacting to activity (e.g., movement sensors)
 | 
			
		||||
  CLIMATE_PRESET_ACTIVITY = 7,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Convert the given ClimateMode to a human-readable string.
 | 
			
		||||
 
 | 
			
		||||
@@ -4,50 +4,6 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace climate {
 | 
			
		||||
 | 
			
		||||
bool ClimateTraits::supports_mode(ClimateMode mode) const {
 | 
			
		||||
  switch (mode) {
 | 
			
		||||
    case CLIMATE_MODE_OFF:
 | 
			
		||||
      return true;
 | 
			
		||||
    case CLIMATE_MODE_AUTO:
 | 
			
		||||
      return this->supports_auto_mode_;
 | 
			
		||||
    case CLIMATE_MODE_COOL:
 | 
			
		||||
      return this->supports_cool_mode_;
 | 
			
		||||
    case CLIMATE_MODE_HEAT:
 | 
			
		||||
      return this->supports_heat_mode_;
 | 
			
		||||
    case CLIMATE_MODE_FAN_ONLY:
 | 
			
		||||
      return this->supports_fan_only_mode_;
 | 
			
		||||
    case CLIMATE_MODE_DRY:
 | 
			
		||||
      return this->supports_dry_mode_;
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ClimateTraits::get_supports_current_temperature() const { return supports_current_temperature_; }
 | 
			
		||||
void ClimateTraits::set_supports_current_temperature(bool supports_current_temperature) {
 | 
			
		||||
  supports_current_temperature_ = supports_current_temperature;
 | 
			
		||||
}
 | 
			
		||||
bool ClimateTraits::get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; }
 | 
			
		||||
void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
 | 
			
		||||
  supports_two_point_target_temperature_ = supports_two_point_target_temperature;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; }
 | 
			
		||||
void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; }
 | 
			
		||||
void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; }
 | 
			
		||||
void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) {
 | 
			
		||||
  supports_fan_only_mode_ = supports_fan_only_mode;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_dry_mode(bool supports_dry_mode) { supports_dry_mode_ = supports_dry_mode; }
 | 
			
		||||
void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; }
 | 
			
		||||
void ClimateTraits::set_supports_action(bool supports_action) { supports_action_ = supports_action; }
 | 
			
		||||
float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; }
 | 
			
		||||
void ClimateTraits::set_visual_min_temperature(float visual_min_temperature) {
 | 
			
		||||
  visual_min_temperature_ = visual_min_temperature;
 | 
			
		||||
}
 | 
			
		||||
float ClimateTraits::get_visual_max_temperature() const { return visual_max_temperature_; }
 | 
			
		||||
void ClimateTraits::set_visual_max_temperature(float visual_max_temperature) {
 | 
			
		||||
  visual_max_temperature_ = visual_max_temperature;
 | 
			
		||||
}
 | 
			
		||||
float ClimateTraits::get_visual_temperature_step() const { return visual_temperature_step_; }
 | 
			
		||||
int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
 | 
			
		||||
  // use printf %g to find number of digits based on temperature step
 | 
			
		||||
  char buf[32];
 | 
			
		||||
@@ -59,160 +15,6 @@ int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
 | 
			
		||||
 | 
			
		||||
  return str.length() - dot_pos - 1;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; }
 | 
			
		||||
bool ClimateTraits::get_supports_away() const { return supports_away_; }
 | 
			
		||||
bool ClimateTraits::get_supports_action() const { return supports_action_; }
 | 
			
		||||
 | 
			
		||||
void ClimateTraits::set_supports_fan_mode_on(bool supports_fan_mode_on) {
 | 
			
		||||
  this->supports_fan_mode_on_ = supports_fan_mode_on;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_fan_mode_off(bool supports_fan_mode_off) {
 | 
			
		||||
  this->supports_fan_mode_off_ = supports_fan_mode_off;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_fan_mode_auto(bool supports_fan_mode_auto) {
 | 
			
		||||
  this->supports_fan_mode_auto_ = supports_fan_mode_auto;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_fan_mode_low(bool supports_fan_mode_low) {
 | 
			
		||||
  this->supports_fan_mode_low_ = supports_fan_mode_low;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_fan_mode_medium(bool supports_fan_mode_medium) {
 | 
			
		||||
  this->supports_fan_mode_medium_ = supports_fan_mode_medium;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_fan_mode_high(bool supports_fan_mode_high) {
 | 
			
		||||
  this->supports_fan_mode_high_ = supports_fan_mode_high;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_fan_mode_middle(bool supports_fan_mode_middle) {
 | 
			
		||||
  this->supports_fan_mode_middle_ = supports_fan_mode_middle;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_fan_mode_focus(bool supports_fan_mode_focus) {
 | 
			
		||||
  this->supports_fan_mode_focus_ = supports_fan_mode_focus;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) {
 | 
			
		||||
  this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse;
 | 
			
		||||
}
 | 
			
		||||
bool ClimateTraits::supports_fan_mode(ClimateFanMode fan_mode) const {
 | 
			
		||||
  switch (fan_mode) {
 | 
			
		||||
    case climate::CLIMATE_FAN_ON:
 | 
			
		||||
      return this->supports_fan_mode_on_;
 | 
			
		||||
    case climate::CLIMATE_FAN_OFF:
 | 
			
		||||
      return this->supports_fan_mode_off_;
 | 
			
		||||
    case climate::CLIMATE_FAN_AUTO:
 | 
			
		||||
      return this->supports_fan_mode_auto_;
 | 
			
		||||
    case climate::CLIMATE_FAN_LOW:
 | 
			
		||||
      return this->supports_fan_mode_low_;
 | 
			
		||||
    case climate::CLIMATE_FAN_MEDIUM:
 | 
			
		||||
      return this->supports_fan_mode_medium_;
 | 
			
		||||
    case climate::CLIMATE_FAN_HIGH:
 | 
			
		||||
      return this->supports_fan_mode_high_;
 | 
			
		||||
    case climate::CLIMATE_FAN_MIDDLE:
 | 
			
		||||
      return this->supports_fan_mode_middle_;
 | 
			
		||||
    case climate::CLIMATE_FAN_FOCUS:
 | 
			
		||||
      return this->supports_fan_mode_focus_;
 | 
			
		||||
    case climate::CLIMATE_FAN_DIFFUSE:
 | 
			
		||||
      return this->supports_fan_mode_diffuse_;
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ClimateTraits::get_supports_fan_modes() const {
 | 
			
		||||
  return this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ ||
 | 
			
		||||
         this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ ||
 | 
			
		||||
         this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supported_custom_fan_modes(std::vector<std::string> &supported_custom_fan_modes) {
 | 
			
		||||
  this->supported_custom_fan_modes_ = supported_custom_fan_modes;
 | 
			
		||||
}
 | 
			
		||||
const std::vector<std::string> ClimateTraits::get_supported_custom_fan_modes() const {
 | 
			
		||||
  return this->supported_custom_fan_modes_;
 | 
			
		||||
}
 | 
			
		||||
bool ClimateTraits::supports_custom_fan_mode(std::string &custom_fan_mode) const {
 | 
			
		||||
  return std::count(this->supported_custom_fan_modes_.begin(), this->supported_custom_fan_modes_.end(),
 | 
			
		||||
                    custom_fan_mode);
 | 
			
		||||
}
 | 
			
		||||
bool ClimateTraits::supports_preset(ClimatePreset preset) const {
 | 
			
		||||
  switch (preset) {
 | 
			
		||||
    case climate::CLIMATE_PRESET_ECO:
 | 
			
		||||
      return this->supports_preset_eco_;
 | 
			
		||||
    case climate::CLIMATE_PRESET_AWAY:
 | 
			
		||||
      return this->supports_preset_away_;
 | 
			
		||||
    case climate::CLIMATE_PRESET_BOOST:
 | 
			
		||||
      return this->supports_preset_boost_;
 | 
			
		||||
    case climate::CLIMATE_PRESET_COMFORT:
 | 
			
		||||
      return this->supports_preset_comfort_;
 | 
			
		||||
    case climate::CLIMATE_PRESET_HOME:
 | 
			
		||||
      return this->supports_preset_home_;
 | 
			
		||||
    case climate::CLIMATE_PRESET_SLEEP:
 | 
			
		||||
      return this->supports_preset_sleep_;
 | 
			
		||||
    case climate::CLIMATE_PRESET_ACTIVITY:
 | 
			
		||||
      return this->supports_preset_activity_;
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_preset_eco(bool supports_preset_eco) {
 | 
			
		||||
  this->supports_preset_eco_ = supports_preset_eco;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_preset_away(bool supports_preset_away) {
 | 
			
		||||
  this->supports_preset_away_ = supports_preset_away;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_preset_boost(bool supports_preset_boost) {
 | 
			
		||||
  this->supports_preset_boost_ = supports_preset_boost;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_preset_comfort(bool supports_preset_comfort) {
 | 
			
		||||
  this->supports_preset_comfort_ = supports_preset_comfort;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_preset_home(bool supports_preset_home) {
 | 
			
		||||
  this->supports_preset_home_ = supports_preset_home;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_preset_sleep(bool supports_preset_sleep) {
 | 
			
		||||
  this->supports_preset_sleep_ = supports_preset_sleep;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_preset_activity(bool supports_preset_activity) {
 | 
			
		||||
  this->supports_preset_activity_ = supports_preset_activity;
 | 
			
		||||
}
 | 
			
		||||
bool ClimateTraits::get_supports_presets() const {
 | 
			
		||||
  return this->supports_preset_eco_ || this->supports_preset_away_ || this->supports_preset_boost_ ||
 | 
			
		||||
         this->supports_preset_comfort_ || this->supports_preset_home_ || this->supports_preset_sleep_ ||
 | 
			
		||||
         this->supports_preset_activity_;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supported_custom_presets(std::vector<std::string> &supported_custom_presets) {
 | 
			
		||||
  this->supported_custom_presets_ = supported_custom_presets;
 | 
			
		||||
}
 | 
			
		||||
const std::vector<std::string> ClimateTraits::get_supported_custom_presets() const {
 | 
			
		||||
  return this->supported_custom_presets_;
 | 
			
		||||
}
 | 
			
		||||
bool ClimateTraits::supports_custom_preset(std::string &custom_preset) const {
 | 
			
		||||
  return std::count(this->supported_custom_presets_.begin(), this->supported_custom_presets_.end(), custom_preset);
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_swing_mode_off(bool supports_swing_mode_off) {
 | 
			
		||||
  this->supports_swing_mode_off_ = supports_swing_mode_off;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_swing_mode_both(bool supports_swing_mode_both) {
 | 
			
		||||
  this->supports_swing_mode_both_ = supports_swing_mode_both;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_swing_mode_vertical(bool supports_swing_mode_vertical) {
 | 
			
		||||
  this->supports_swing_mode_vertical_ = supports_swing_mode_vertical;
 | 
			
		||||
}
 | 
			
		||||
void ClimateTraits::set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal) {
 | 
			
		||||
  this->supports_swing_mode_horizontal_ = supports_swing_mode_horizontal;
 | 
			
		||||
}
 | 
			
		||||
bool ClimateTraits::supports_swing_mode(ClimateSwingMode swing_mode) const {
 | 
			
		||||
  switch (swing_mode) {
 | 
			
		||||
    case climate::CLIMATE_SWING_OFF:
 | 
			
		||||
      return this->supports_swing_mode_off_;
 | 
			
		||||
    case climate::CLIMATE_SWING_BOTH:
 | 
			
		||||
      return this->supports_swing_mode_both_;
 | 
			
		||||
    case climate::CLIMATE_SWING_VERTICAL:
 | 
			
		||||
      return this->supports_swing_mode_vertical_;
 | 
			
		||||
    case climate::CLIMATE_SWING_HORIZONTAL:
 | 
			
		||||
      return this->supports_swing_mode_horizontal_;
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ClimateTraits::get_supports_swing_modes() const {
 | 
			
		||||
  return this->supports_swing_mode_off_ || this->supports_swing_mode_both_ || supports_swing_mode_vertical_ ||
 | 
			
		||||
         supports_swing_mode_horizontal_;
 | 
			
		||||
}
 | 
			
		||||
}  // namespace climate
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user