mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 09:01:49 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			2025.5.0
			...
			jesserockz
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					273343b182 | 
							
								
								
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							@@ -47,7 +47,7 @@ runs:
 | 
			
		||||
 | 
			
		||||
    - name: Build and push to ghcr by digest
 | 
			
		||||
      id: build-ghcr
 | 
			
		||||
      uses: docker/build-push-action@v6.17.0
 | 
			
		||||
      uses: docker/build-push-action@v6.16.0
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_BUILD_SUMMARY: false
 | 
			
		||||
        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
			
		||||
@@ -73,7 +73,7 @@ runs:
 | 
			
		||||
 | 
			
		||||
    - name: Build and push to dockerhub by digest
 | 
			
		||||
      id: build-dockerhub
 | 
			
		||||
      uses: docker/build-push-action@v6.17.0
 | 
			
		||||
      uses: docker/build-push-action@v6.16.0
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_BUILD_SUMMARY: false
 | 
			
		||||
        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							@@ -57,17 +57,6 @@ jobs:
 | 
			
		||||
              event: 'REQUEST_CHANGES',
 | 
			
		||||
              body: 'You have altered the generated proto files but they do not match what is expected.\nPlease run "script/api_protobuf/api_protobuf.py" and commit the changes.'
 | 
			
		||||
            })
 | 
			
		||||
      - if: failure()
 | 
			
		||||
        name: Show changes
 | 
			
		||||
        run: git diff
 | 
			
		||||
      - if: failure()
 | 
			
		||||
        name: Archive artifacts
 | 
			
		||||
        uses: actions/upload-artifact@v4.6.2
 | 
			
		||||
        with:
 | 
			
		||||
          name: generated-proto-files
 | 
			
		||||
          path: |
 | 
			
		||||
            esphome/components/api/api_pb2.*
 | 
			
		||||
            esphome/components/api/api_pb2_service.*
 | 
			
		||||
      - if: success()
 | 
			
		||||
        name: Dismiss review
 | 
			
		||||
        uses: actions/github-script@v7.0.1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -292,11 +292,6 @@ jobs:
 | 
			
		||||
            name: Run script/clang-tidy for ESP32 IDF
 | 
			
		||||
            options: --environment esp32-idf-tidy --grep USE_ESP_IDF
 | 
			
		||||
            pio_cache_key: tidyesp32-idf
 | 
			
		||||
          - id: clang-tidy
 | 
			
		||||
            name: Run script/clang-tidy for ZEPHYR
 | 
			
		||||
            options: --environment nrf52-tidy --grep USE_ZEPHYR
 | 
			
		||||
            pio_cache_key: tidy-zephyr
 | 
			
		||||
            ignore_errors: true
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
@@ -336,13 +331,13 @@ jobs:
 | 
			
		||||
      - name: Run clang-tidy
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
 | 
			
		||||
          script/clang-tidy --all-headers --fix ${{ matrix.options }}
 | 
			
		||||
        env:
 | 
			
		||||
          # Also cache libdeps, store them in a ~/.platformio subfolder
 | 
			
		||||
          PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
 | 
			
		||||
 | 
			
		||||
      - name: Suggested changes
 | 
			
		||||
        run: script/ci-suggest-changes ${{ matrix.ignore_errors && '|| true' || '' }}
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
        # yamllint disable-line rule:line-length
 | 
			
		||||
        if: always()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,7 +18,6 @@ jobs:
 | 
			
		||||
    outputs:
 | 
			
		||||
      tag: ${{ steps.tag.outputs.tag }}
 | 
			
		||||
      branch_build: ${{ steps.tag.outputs.branch_build }}
 | 
			
		||||
      deploy_env: ${{ steps.tag.outputs.deploy_env }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Get tag
 | 
			
		||||
@@ -28,11 +27,6 @@ jobs:
 | 
			
		||||
          if [[ "${{ github.event_name }}" = "release" ]]; then
 | 
			
		||||
            TAG="${{ github.event.release.tag_name}}"
 | 
			
		||||
            BRANCH_BUILD="false"
 | 
			
		||||
            if [[ "${{ github.event.release.prerelease }}" = "true" ]]; then
 | 
			
		||||
              ENVIRONMENT="beta"
 | 
			
		||||
            else
 | 
			
		||||
              ENVIRONMENT="production"
 | 
			
		||||
            fi
 | 
			
		||||
          else
 | 
			
		||||
            TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
 | 
			
		||||
            today="$(date --utc '+%Y%m%d')"
 | 
			
		||||
@@ -41,15 +35,12 @@ jobs:
 | 
			
		||||
            if [[ "$BRANCH" != "dev" ]]; then
 | 
			
		||||
              TAG="${TAG}-${BRANCH}"
 | 
			
		||||
              BRANCH_BUILD="true"
 | 
			
		||||
              ENVIRONMENT=""
 | 
			
		||||
            else
 | 
			
		||||
              BRANCH_BUILD="false"
 | 
			
		||||
              ENVIRONMENT="dev"
 | 
			
		||||
            fi
 | 
			
		||||
          fi
 | 
			
		||||
          echo "tag=${TAG}" >> $GITHUB_OUTPUT
 | 
			
		||||
          echo "branch_build=${BRANCH_BUILD}" >> $GITHUB_OUTPUT
 | 
			
		||||
          echo "deploy_env=${ENVIRONMENT}" >> $GITHUB_OUTPUT
 | 
			
		||||
        # yamllint enable rule:line-length
 | 
			
		||||
 | 
			
		||||
  deploy-pypi:
 | 
			
		||||
@@ -65,14 +56,16 @@ jobs:
 | 
			
		||||
        uses: actions/setup-python@v5.6.0
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: "3.x"
 | 
			
		||||
      - name: Set up python environment
 | 
			
		||||
        env:
 | 
			
		||||
          ESPHOME_NO_VENV: 1
 | 
			
		||||
        run: script/setup
 | 
			
		||||
      - name: Build
 | 
			
		||||
        run: |-
 | 
			
		||||
          pip3 install build
 | 
			
		||||
          python3 -m build
 | 
			
		||||
      - name: Publish
 | 
			
		||||
        uses: pypa/gh-action-pypi-publish@v1.12.4
 | 
			
		||||
        with:
 | 
			
		||||
          skip-existing: true
 | 
			
		||||
 | 
			
		||||
  deploy-docker:
 | 
			
		||||
    name: Build ESPHome ${{ matrix.platform.arch }}
 | 
			
		||||
@@ -238,24 +231,3 @@ jobs:
 | 
			
		||||
                content: description
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
  deploy-esphome-schema:
 | 
			
		||||
    if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [init]
 | 
			
		||||
    environment: ${{ needs.init.outputs.deploy_env }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Trigger Workflow
 | 
			
		||||
        uses: actions/github-script@v7.0.1
 | 
			
		||||
        with:
 | 
			
		||||
          github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
 | 
			
		||||
          script: |
 | 
			
		||||
            github.rest.actions.createWorkflowDispatch({
 | 
			
		||||
              owner: "esphome",
 | 
			
		||||
              repo: "esphome-schema",
 | 
			
		||||
              workflow_id: "generate-schemas.yml",
 | 
			
		||||
              ref: "main",
 | 
			
		||||
              inputs: {
 | 
			
		||||
                version: "${{ needs.init.outputs.tag }}",
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -143,4 +143,3 @@ sdkconfig.*
 | 
			
		||||
/components
 | 
			
		||||
/managed_components
 | 
			
		||||
 | 
			
		||||
api-docs/
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
repos:
 | 
			
		||||
  - repo: https://github.com/astral-sh/ruff-pre-commit
 | 
			
		||||
    # Ruff version.
 | 
			
		||||
    rev: v0.11.9
 | 
			
		||||
    rev: v0.11.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      # Run the linter.
 | 
			
		||||
      - id: ruff
 | 
			
		||||
@@ -33,7 +33,7 @@ repos:
 | 
			
		||||
      - id: pyupgrade
 | 
			
		||||
        args: [--py39-plus]
 | 
			
		||||
  - repo: https://github.com/adrienverge/yamllint.git
 | 
			
		||||
    rev: v1.37.1
 | 
			
		||||
    rev: v1.35.1
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: yamllint
 | 
			
		||||
  - repo: https://github.com/pre-commit/mirrors-clang-format
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								CODEOWNERS
									
									
									
									
									
								
							@@ -169,7 +169,7 @@ esphome/components/gp2y1010au0f/* @zry98
 | 
			
		||||
esphome/components/gp8403/* @jesserockz
 | 
			
		||||
esphome/components/gpio/* @esphome/core
 | 
			
		||||
esphome/components/gpio/one_wire/* @ssieb
 | 
			
		||||
esphome/components/gps/* @coogle @ximex
 | 
			
		||||
esphome/components/gps/* @coogle
 | 
			
		||||
esphome/components/graph/* @synco
 | 
			
		||||
esphome/components/graphical_display_menu/* @MrMDavidson
 | 
			
		||||
esphome/components/gree/* @orestismers
 | 
			
		||||
@@ -278,11 +278,10 @@ esphome/components/mdns/* @esphome/core
 | 
			
		||||
esphome/components/media_player/* @jesserockz
 | 
			
		||||
esphome/components/micro_wake_word/* @jesserockz @kahrendt
 | 
			
		||||
esphome/components/micronova/* @jorre05
 | 
			
		||||
esphome/components/microphone/* @jesserockz @kahrendt
 | 
			
		||||
esphome/components/microphone/* @jesserockz
 | 
			
		||||
esphome/components/mics_4514/* @jesserockz
 | 
			
		||||
esphome/components/midea/* @dudanov
 | 
			
		||||
esphome/components/midea_ir/* @dudanov
 | 
			
		||||
esphome/components/mipi_spi/* @clydebarrow
 | 
			
		||||
esphome/components/mitsubishi/* @RubyBailey
 | 
			
		||||
esphome/components/mixer/speaker/* @kahrendt
 | 
			
		||||
esphome/components/mlx90393/* @functionpointer
 | 
			
		||||
@@ -320,7 +319,6 @@ esphome/components/online_image/* @clydebarrow @guillempages
 | 
			
		||||
esphome/components/opentherm/* @olegtarasov
 | 
			
		||||
esphome/components/ota/* @esphome/core
 | 
			
		||||
esphome/components/output/* @esphome/core
 | 
			
		||||
esphome/components/packet_transport/* @clydebarrow
 | 
			
		||||
esphome/components/pca6416a/* @Mat931
 | 
			
		||||
esphome/components/pca9554/* @clydebarrow @hwstar
 | 
			
		||||
esphome/components/pcf85063/* @brogon
 | 
			
		||||
@@ -330,7 +328,6 @@ esphome/components/pipsolar/* @andreashergert1984
 | 
			
		||||
esphome/components/pm1006/* @habbie
 | 
			
		||||
esphome/components/pm2005/* @andrewjswan
 | 
			
		||||
esphome/components/pmsa003i/* @sjtrny
 | 
			
		||||
esphome/components/pmsx003/* @ximex
 | 
			
		||||
esphome/components/pmwcs3/* @SeByDocKy
 | 
			
		||||
esphome/components/pn532/* @OttoWinter @jesserockz
 | 
			
		||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
 | 
			
		||||
@@ -399,7 +396,6 @@ esphome/components/smt100/* @piechade
 | 
			
		||||
esphome/components/sn74hc165/* @jesserockz
 | 
			
		||||
esphome/components/socket/* @esphome/core
 | 
			
		||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
 | 
			
		||||
esphome/components/sound_level/* @kahrendt
 | 
			
		||||
esphome/components/speaker/* @jesserockz @kahrendt
 | 
			
		||||
esphome/components/speaker/media_player/* @kahrendt @synesthesiam
 | 
			
		||||
esphome/components/spi/* @clydebarrow @esphome/core
 | 
			
		||||
@@ -431,7 +427,6 @@ esphome/components/sun/* @OttoWinter
 | 
			
		||||
esphome/components/sun_gtil2/* @Mat931
 | 
			
		||||
esphome/components/switch/* @esphome/core
 | 
			
		||||
esphome/components/switch/binary_sensor/* @ssieb
 | 
			
		||||
esphome/components/syslog/* @clydebarrow
 | 
			
		||||
esphome/components/t6615/* @tylermenezes
 | 
			
		||||
esphome/components/tc74/* @sethgirvan
 | 
			
		||||
esphome/components/tca9548a/* @andreashergert1984
 | 
			
		||||
@@ -471,7 +466,6 @@ esphome/components/tuya/switch/* @jesserockz
 | 
			
		||||
esphome/components/tuya/text_sensor/* @dentra
 | 
			
		||||
esphome/components/uart/* @esphome/core
 | 
			
		||||
esphome/components/uart/button/* @ssieb
 | 
			
		||||
esphome/components/uart/packet_transport/* @clydebarrow
 | 
			
		||||
esphome/components/udp/* @clydebarrow
 | 
			
		||||
esphome/components/ufire_ec/* @pvizeli
 | 
			
		||||
esphome/components/ufire_ise/* @pvizeli
 | 
			
		||||
 
 | 
			
		||||
@@ -11,9 +11,7 @@ FROM base-source-${BUILD_TYPE} AS base
 | 
			
		||||
 | 
			
		||||
RUN git config --system --add safe.directory "*"
 | 
			
		||||
 | 
			
		||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
 | 
			
		||||
 | 
			
		||||
RUN pip install --no-cache-dir -U pip uv==0.6.14
 | 
			
		||||
RUN pip install uv==0.6.14
 | 
			
		||||
 | 
			
		||||
COPY requirements.txt /
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ from esphome.const import (
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, EsphomeError, coroutine
 | 
			
		||||
from esphome.helpers import get_bool_env, indent, is_ip_address
 | 
			
		||||
from esphome.log import AnsiFore, color, setup_log
 | 
			
		||||
from esphome.log import Fore, color, setup_log
 | 
			
		||||
from esphome.util import (
 | 
			
		||||
    get_serial_ports,
 | 
			
		||||
    list_yaml_files,
 | 
			
		||||
@@ -83,7 +83,7 @@ def choose_prompt(options, purpose: str = None):
 | 
			
		||||
                raise ValueError
 | 
			
		||||
            break
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            safe_print(color(AnsiFore.RED, f"Invalid option: '{opt}'"))
 | 
			
		||||
            safe_print(color(Fore.RED, f"Invalid option: '{opt}'"))
 | 
			
		||||
    return options[opt - 1][1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -596,30 +596,30 @@ def command_update_all(args):
 | 
			
		||||
        click.echo(f"{half_line}{middle_text}{half_line}")
 | 
			
		||||
 | 
			
		||||
    for f in files:
 | 
			
		||||
        print(f"Updating {color(AnsiFore.CYAN, f)}")
 | 
			
		||||
        print(f"Updating {color(Fore.CYAN, f)}")
 | 
			
		||||
        print("-" * twidth)
 | 
			
		||||
        print()
 | 
			
		||||
        rc = run_external_process(
 | 
			
		||||
            "esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
 | 
			
		||||
        )
 | 
			
		||||
        if rc == 0:
 | 
			
		||||
            print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {f}")
 | 
			
		||||
            print_bar(f"[{color(Fore.BOLD_GREEN, 'SUCCESS')}] {f}")
 | 
			
		||||
            success[f] = True
 | 
			
		||||
        else:
 | 
			
		||||
            print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {f}")
 | 
			
		||||
            print_bar(f"[{color(Fore.BOLD_RED, 'ERROR')}] {f}")
 | 
			
		||||
            success[f] = False
 | 
			
		||||
 | 
			
		||||
        print()
 | 
			
		||||
        print()
 | 
			
		||||
        print()
 | 
			
		||||
 | 
			
		||||
    print_bar(f"[{color(AnsiFore.BOLD_WHITE, 'SUMMARY')}]")
 | 
			
		||||
    print_bar(f"[{color(Fore.BOLD_WHITE, 'SUMMARY')}]")
 | 
			
		||||
    failed = 0
 | 
			
		||||
    for f in files:
 | 
			
		||||
        if success[f]:
 | 
			
		||||
            print(f"  - {f}: {color(AnsiFore.GREEN, 'SUCCESS')}")
 | 
			
		||||
            print(f"  - {f}: {color(Fore.GREEN, 'SUCCESS')}")
 | 
			
		||||
        else:
 | 
			
		||||
            print(f"  - {f}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
 | 
			
		||||
            print(f"  - {f}: {color(Fore.BOLD_RED, 'FAILED')}")
 | 
			
		||||
            failed += 1
 | 
			
		||||
    return failed
 | 
			
		||||
 | 
			
		||||
@@ -645,7 +645,7 @@ def command_rename(args, config):
 | 
			
		||||
        if c not in ALLOWED_NAME_CHARS:
 | 
			
		||||
            print(
 | 
			
		||||
                color(
 | 
			
		||||
                    AnsiFore.BOLD_RED,
 | 
			
		||||
                    Fore.BOLD_RED,
 | 
			
		||||
                    f"'{c}' is an invalid character for names. Valid characters are: "
 | 
			
		||||
                    f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)",
 | 
			
		||||
                )
 | 
			
		||||
@@ -658,9 +658,7 @@ def command_rename(args, config):
 | 
			
		||||
    yaml = yaml_util.load_yaml(CORE.config_path)
 | 
			
		||||
    if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
 | 
			
		||||
        print(
 | 
			
		||||
            color(
 | 
			
		||||
                AnsiFore.BOLD_RED, "Complex YAML files cannot be automatically renamed."
 | 
			
		||||
            )
 | 
			
		||||
            color(Fore.BOLD_RED, "Complex YAML files cannot be automatically renamed.")
 | 
			
		||||
        )
 | 
			
		||||
        return 1
 | 
			
		||||
    old_name = yaml[CONF_ESPHOME][CONF_NAME]
 | 
			
		||||
@@ -683,7 +681,7 @@ def command_rename(args, config):
 | 
			
		||||
            )
 | 
			
		||||
            > 1
 | 
			
		||||
        ):
 | 
			
		||||
            print(color(AnsiFore.BOLD_RED, "Too many matches in YAML to safely rename"))
 | 
			
		||||
            print(color(Fore.BOLD_RED, "Too many matches in YAML to safely rename"))
 | 
			
		||||
            return 1
 | 
			
		||||
 | 
			
		||||
        new_raw = re.sub(
 | 
			
		||||
@@ -695,7 +693,7 @@ def command_rename(args, config):
 | 
			
		||||
 | 
			
		||||
    new_path = os.path.join(CORE.config_dir, args.name + ".yaml")
 | 
			
		||||
    print(
 | 
			
		||||
        f"Updating {color(AnsiFore.CYAN, CORE.config_path)} to {color(AnsiFore.CYAN, new_path)}"
 | 
			
		||||
        f"Updating {color(Fore.CYAN, CORE.config_path)} to {color(Fore.CYAN, new_path)}"
 | 
			
		||||
    )
 | 
			
		||||
    print()
 | 
			
		||||
 | 
			
		||||
@@ -704,7 +702,7 @@ def command_rename(args, config):
 | 
			
		||||
 | 
			
		||||
    rc = run_external_process("esphome", "config", new_path)
 | 
			
		||||
    if rc != 0:
 | 
			
		||||
        print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
 | 
			
		||||
        print(color(Fore.BOLD_RED, "Rename failed. Reverting changes."))
 | 
			
		||||
        os.remove(new_path)
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
@@ -730,7 +728,7 @@ def command_rename(args, config):
 | 
			
		||||
    if CORE.config_path != new_path:
 | 
			
		||||
        os.remove(CORE.config_path)
 | 
			
		||||
 | 
			
		||||
    print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
 | 
			
		||||
    print(color(Fore.BOLD_GREEN, "SUCCESS"))
 | 
			
		||||
    print()
 | 
			
		||||
    return 0
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,10 +47,9 @@ SAMPLING_MODES = {
 | 
			
		||||
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
 | 
			
		||||
adc2_channel_t = cg.global_ns.enum("adc2_channel_t")
 | 
			
		||||
 | 
			
		||||
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
 | 
			
		||||
# pin to adc1 channel mapping
 | 
			
		||||
# https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/include/driver/adc.h
 | 
			
		||||
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32: {
 | 
			
		||||
        36: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        37: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
@@ -61,41 +60,6 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
			
		||||
        34: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        35: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32C2: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32C6: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32H2: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32S2: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
@@ -108,7 +72,6 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32S3: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
@@ -121,12 +84,40 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C2: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C6: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32H2: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# pin to adc2 channel mapping
 | 
			
		||||
# https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/include/driver/adc.h
 | 
			
		||||
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h
 | 
			
		||||
    # TODO: add other variants
 | 
			
		||||
    VARIANT_ESP32: {
 | 
			
		||||
        4: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
        0: adc2_channel_t.ADC2_CHANNEL_1,
 | 
			
		||||
@@ -139,19 +130,6 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
			
		||||
        25: adc2_channel_t.ADC2_CHANNEL_8,
 | 
			
		||||
        26: adc2_channel_t.ADC2_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32C2: {
 | 
			
		||||
        5: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        5: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32C6: {},  # no ADC2
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32H2: {},  # no ADC2
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32S2: {
 | 
			
		||||
        11: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
        12: adc2_channel_t.ADC2_CHANNEL_1,
 | 
			
		||||
@@ -164,7 +142,6 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
			
		||||
        19: adc2_channel_t.ADC2_CHANNEL_8,
 | 
			
		||||
        20: adc2_channel_t.ADC2_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32S3: {
 | 
			
		||||
        11: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
        12: adc2_channel_t.ADC2_CHANNEL_1,
 | 
			
		||||
@@ -177,6 +154,12 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
			
		||||
        19: adc2_channel_t.ADC2_CHANNEL_8,
 | 
			
		||||
        20: adc2_channel_t.ADC2_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        5: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C2: {},
 | 
			
		||||
    VARIANT_ESP32C6: {},
 | 
			
		||||
    VARIANT_ESP32H2: {},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ AirthingsWaveBase = airthings_wave_base_ns.class_(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BASE_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
    sensor.SENSOR_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_PERCENT,
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,6 @@ from esphome.components import mqtt, web_server
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_CODE,
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_ON_STATE,
 | 
			
		||||
@@ -14,7 +12,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_WEB_SERVER,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.cpp_generator import MockObjClass
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@grahambrown11", "@hwstar"]
 | 
			
		||||
@@ -81,11 +78,12 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_(
 | 
			
		||||
    "AlarmControlPanelCondition", automation.Condition
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_ALARM_CONTROL_PANEL_SCHEMA = (
 | 
			
		||||
ALARM_CONTROL_PANEL_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(AlarmControlPanel),
 | 
			
		||||
            cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
 | 
			
		||||
                mqtt.MQTTAlarmControlPanelComponent
 | 
			
		||||
            ),
 | 
			
		||||
@@ -148,33 +146,6 @@ _ALARM_CONTROL_PANEL_SCHEMA = (
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def alarm_control_panel_schema(
 | 
			
		||||
    class_: MockObjClass,
 | 
			
		||||
    *,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(class_),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for key, default, validator in [
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_ICON, icon, cv.icon),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return _ALARM_CONTROL_PANEL_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
ALARM_CONTROL_PANEL_SCHEMA = alarm_control_panel_schema(AlarmControlPanel)
 | 
			
		||||
ALARM_CONTROL_PANEL_SCHEMA.add_extra(
 | 
			
		||||
    cv.deprecated_schema_constant("alarm_control_panel")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(AlarmControlPanel),
 | 
			
		||||
@@ -238,12 +209,6 @@ async def register_alarm_control_panel(var, config):
 | 
			
		||||
    await setup_alarm_control_panel_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def new_alarm_control_panel(config, *args):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], *args)
 | 
			
		||||
    await register_alarm_control_panel(var, config)
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "alarm_control_panel.arm_away", ArmAwayAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import ble_client, cover
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_PIN
 | 
			
		||||
from esphome.const import CONF_ID, CONF_PIN
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@buxtronix"]
 | 
			
		||||
DEPENDENCIES = ["ble_client"]
 | 
			
		||||
@@ -15,9 +15,9 @@ Am43Component = am43_ns.class_(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cover.cover_schema(Am43Component)
 | 
			
		||||
    .extend(
 | 
			
		||||
    cover.COVER_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Am43Component),
 | 
			
		||||
            cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF),
 | 
			
		||||
            cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
@@ -28,8 +28,9 @@ CONFIG_SCHEMA = (
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await cover.new_cover(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    cg.add(var.set_pin(config[CONF_PIN]))
 | 
			
		||||
    cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await cover.register_cover(var, config)
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import ble_client, climate
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_UNIT_OF_MEASUREMENT
 | 
			
		||||
from esphome.const import CONF_ID, CONF_UNIT_OF_MEASUREMENT
 | 
			
		||||
 | 
			
		||||
UNITS = {
 | 
			
		||||
    "f": "f",
 | 
			
		||||
@@ -17,9 +17,9 @@ Anova = anova_ns.class_(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    climate.climate_schema(Anova)
 | 
			
		||||
    .extend(
 | 
			
		||||
    climate.CLIMATE_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Anova),
 | 
			
		||||
            cv.Required(CONF_UNIT_OF_MEASUREMENT): cv.enum(UNITS),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
@@ -29,7 +29,8 @@ CONFIG_SCHEMA = (
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await climate.new_climate(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await climate.register_climate(var, config)
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
    cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT]))
 | 
			
		||||
 
 | 
			
		||||
@@ -33,24 +33,23 @@ service APIConnection {
 | 
			
		||||
  rpc execute_service (ExecuteServiceRequest) returns (void) {}
 | 
			
		||||
  rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
 | 
			
		||||
 | 
			
		||||
  rpc button_command (ButtonCommandRequest) returns (void) {}
 | 
			
		||||
  rpc camera_image (CameraImageRequest) returns (void) {}
 | 
			
		||||
  rpc climate_command (ClimateCommandRequest) returns (void) {}
 | 
			
		||||
  rpc cover_command (CoverCommandRequest) returns (void) {}
 | 
			
		||||
  rpc date_command (DateCommandRequest) returns (void) {}
 | 
			
		||||
  rpc datetime_command (DateTimeCommandRequest) returns (void) {}
 | 
			
		||||
  rpc fan_command (FanCommandRequest) returns (void) {}
 | 
			
		||||
  rpc light_command (LightCommandRequest) returns (void) {}
 | 
			
		||||
  rpc lock_command (LockCommandRequest) returns (void) {}
 | 
			
		||||
  rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
 | 
			
		||||
  rpc number_command (NumberCommandRequest) returns (void) {}
 | 
			
		||||
  rpc select_command (SelectCommandRequest) returns (void) {}
 | 
			
		||||
  rpc siren_command (SirenCommandRequest) returns (void) {}
 | 
			
		||||
  rpc switch_command (SwitchCommandRequest) returns (void) {}
 | 
			
		||||
  rpc camera_image (CameraImageRequest) returns (void) {}
 | 
			
		||||
  rpc climate_command (ClimateCommandRequest) returns (void) {}
 | 
			
		||||
  rpc number_command (NumberCommandRequest) returns (void) {}
 | 
			
		||||
  rpc text_command (TextCommandRequest) returns (void) {}
 | 
			
		||||
  rpc time_command (TimeCommandRequest) returns (void) {}
 | 
			
		||||
  rpc update_command (UpdateCommandRequest) returns (void) {}
 | 
			
		||||
  rpc select_command (SelectCommandRequest) returns (void) {}
 | 
			
		||||
  rpc button_command (ButtonCommandRequest) returns (void) {}
 | 
			
		||||
  rpc lock_command (LockCommandRequest) returns (void) {}
 | 
			
		||||
  rpc valve_command (ValveCommandRequest) returns (void) {}
 | 
			
		||||
  rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
 | 
			
		||||
  rpc date_command (DateCommandRequest) returns (void) {}
 | 
			
		||||
  rpc time_command (TimeCommandRequest) returns (void) {}
 | 
			
		||||
  rpc datetime_command (DateTimeCommandRequest) returns (void) {}
 | 
			
		||||
  rpc update_command (UpdateCommandRequest) returns (void) {}
 | 
			
		||||
 | 
			
		||||
  rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
 | 
			
		||||
@@ -62,7 +61,6 @@ service APIConnection {
 | 
			
		||||
  rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
 | 
			
		||||
  rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
 | 
			
		||||
  rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_scanner_set_mode(BluetoothScannerSetModeRequest) returns (void) {}
 | 
			
		||||
 | 
			
		||||
  rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
 | 
			
		||||
  rpc voice_assistant_get_configuration(VoiceAssistantConfigurationRequest) returns (VoiceAssistantConfigurationResponse) {}
 | 
			
		||||
@@ -656,7 +654,7 @@ message SubscribeLogsResponse {
 | 
			
		||||
  option (no_delay) = false;
 | 
			
		||||
 | 
			
		||||
  LogLevel level = 1;
 | 
			
		||||
  bytes message = 3;
 | 
			
		||||
  string message = 3;
 | 
			
		||||
  bool send_failed = 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -912,7 +910,6 @@ message ClimateStateResponse {
 | 
			
		||||
  float target_temperature = 4;
 | 
			
		||||
  float target_temperature_low = 5;
 | 
			
		||||
  float target_temperature_high = 6;
 | 
			
		||||
  // For older peers, equal to preset == CLIMATE_PRESET_AWAY
 | 
			
		||||
  bool unused_legacy_away = 7;
 | 
			
		||||
  ClimateAction action = 8;
 | 
			
		||||
  ClimateFanMode fan_mode = 9;
 | 
			
		||||
@@ -938,7 +935,6 @@ message ClimateCommandRequest {
 | 
			
		||||
  float target_temperature_low = 7;
 | 
			
		||||
  bool has_target_temperature_high = 8;
 | 
			
		||||
  float target_temperature_high = 9;
 | 
			
		||||
  // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
 | 
			
		||||
  bool unused_has_legacy_away = 10;
 | 
			
		||||
  bool unused_legacy_away = 11;
 | 
			
		||||
  bool has_fan_mode = 12;
 | 
			
		||||
@@ -1041,49 +1037,6 @@ message SelectCommandRequest {
 | 
			
		||||
  string state = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== SIREN ====================
 | 
			
		||||
message ListEntitiesSirenResponse {
 | 
			
		||||
  option (id) = 55;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_SIREN";
 | 
			
		||||
 | 
			
		||||
  string object_id = 1;
 | 
			
		||||
  fixed32 key = 2;
 | 
			
		||||
  string name = 3;
 | 
			
		||||
  string unique_id = 4;
 | 
			
		||||
 | 
			
		||||
  string icon = 5;
 | 
			
		||||
  bool disabled_by_default = 6;
 | 
			
		||||
  repeated string tones = 7;
 | 
			
		||||
  bool supports_duration = 8;
 | 
			
		||||
  bool supports_volume = 9;
 | 
			
		||||
  EntityCategory entity_category = 10;
 | 
			
		||||
}
 | 
			
		||||
message SirenStateResponse {
 | 
			
		||||
  option (id) = 56;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_SIREN";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  bool state = 2;
 | 
			
		||||
}
 | 
			
		||||
message SirenCommandRequest {
 | 
			
		||||
  option (id) = 57;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_SIREN";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  bool has_state = 2;
 | 
			
		||||
  bool state = 3;
 | 
			
		||||
  bool has_tone = 4;
 | 
			
		||||
  string tone = 5;
 | 
			
		||||
  bool has_duration = 6;
 | 
			
		||||
  uint32 duration = 7;
 | 
			
		||||
  bool has_volume = 8;
 | 
			
		||||
  float volume = 9;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== LOCK ====================
 | 
			
		||||
enum LockState {
 | 
			
		||||
@@ -1253,8 +1206,8 @@ message SubscribeBluetoothLEAdvertisementsRequest {
 | 
			
		||||
 | 
			
		||||
message BluetoothServiceData {
 | 
			
		||||
  string uuid = 1;
 | 
			
		||||
  repeated uint32 legacy_data = 2 [deprecated = true];  // Removed in api version 1.7
 | 
			
		||||
  bytes data = 3;  // Added in api version 1.7
 | 
			
		||||
  repeated uint32 legacy_data = 2 [deprecated = true];
 | 
			
		||||
  bytes data = 3; // Changed in proto version 1.7
 | 
			
		||||
}
 | 
			
		||||
message BluetoothLEAdvertisementResponse {
 | 
			
		||||
  option (id) = 67;
 | 
			
		||||
@@ -1263,7 +1216,7 @@ message BluetoothLEAdvertisementResponse {
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  bytes name = 2;
 | 
			
		||||
  string name = 2;
 | 
			
		||||
  sint32 rssi = 3;
 | 
			
		||||
 | 
			
		||||
  repeated string service_uuids = 4;
 | 
			
		||||
@@ -1519,38 +1472,7 @@ message BluetoothDeviceClearCacheResponse {
 | 
			
		||||
  int32 error = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum BluetoothScannerState {
 | 
			
		||||
  BLUETOOTH_SCANNER_STATE_IDLE = 0;
 | 
			
		||||
  BLUETOOTH_SCANNER_STATE_STARTING = 1;
 | 
			
		||||
  BLUETOOTH_SCANNER_STATE_RUNNING = 2;
 | 
			
		||||
  BLUETOOTH_SCANNER_STATE_FAILED = 3;
 | 
			
		||||
  BLUETOOTH_SCANNER_STATE_STOPPING = 4;
 | 
			
		||||
  BLUETOOTH_SCANNER_STATE_STOPPED = 5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum BluetoothScannerMode {
 | 
			
		||||
  BLUETOOTH_SCANNER_MODE_PASSIVE = 0;
 | 
			
		||||
  BLUETOOTH_SCANNER_MODE_ACTIVE = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothScannerStateResponse {
 | 
			
		||||
  option(id) = 126;
 | 
			
		||||
  option(source) = SOURCE_SERVER;
 | 
			
		||||
  option(ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  BluetoothScannerState state = 1;
 | 
			
		||||
  BluetoothScannerMode mode = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothScannerSetModeRequest {
 | 
			
		||||
  option(id) = 127;
 | 
			
		||||
  option(source) = SOURCE_CLIENT;
 | 
			
		||||
  option(ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  BluetoothScannerMode mode = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== VOICE ASSISTANT ====================
 | 
			
		||||
// ==================== PUSH TO TALK ====================
 | 
			
		||||
enum VoiceAssistantSubscribeFlag {
 | 
			
		||||
  VOICE_ASSISTANT_SUBSCRIBE_NONE = 0;
 | 
			
		||||
  VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1;
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -8,17 +8,13 @@
 | 
			
		||||
#include "api_server.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/entity_base.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
// Keepalive timeout in milliseconds
 | 
			
		||||
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
 | 
			
		||||
 | 
			
		||||
using send_message_t = bool (APIConnection::*)(void *);
 | 
			
		||||
using send_message_t = bool(APIConnection *, void *);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
  This class holds a pointer to the source component that wants to publish a message, and a pointer to a function that
 | 
			
		||||
@@ -34,10 +30,10 @@ class DeferredMessageQueue {
 | 
			
		||||
 | 
			
		||||
   protected:
 | 
			
		||||
    void *source_;
 | 
			
		||||
    send_message_t send_message_;
 | 
			
		||||
    send_message_t *send_message_;
 | 
			
		||||
 | 
			
		||||
   public:
 | 
			
		||||
    DeferredMessage(void *source, send_message_t send_message) : source_(source), send_message_(send_message) {}
 | 
			
		||||
    DeferredMessage(void *source, send_message_t *send_message) : source_(source), send_message_(send_message) {}
 | 
			
		||||
    bool operator==(const DeferredMessage &test) const {
 | 
			
		||||
      return (source_ == test.source_ && send_message_ == test.send_message_);
 | 
			
		||||
    }
 | 
			
		||||
@@ -50,13 +46,12 @@ class DeferredMessageQueue {
 | 
			
		||||
  APIConnection *api_connection_;
 | 
			
		||||
 | 
			
		||||
  // helper for allowing only unique entries in the queue
 | 
			
		||||
  void dmq_push_back_with_dedup_(void *source, send_message_t send_message);
 | 
			
		||||
  void dmq_push_back_with_dedup_(void *source, send_message_t *send_message);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  DeferredMessageQueue(APIConnection *api_connection) : api_connection_(api_connection) {}
 | 
			
		||||
  void process_queue();
 | 
			
		||||
  void defer(void *source, send_message_t send_message);
 | 
			
		||||
  bool empty() const { return deferred_queue_.empty(); }
 | 
			
		||||
  void defer(void *source, send_message_t *send_message);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class APIConnection : public APIServerConnection {
 | 
			
		||||
@@ -74,213 +69,137 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
 | 
			
		||||
  void send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_binary_sensor_state_(binary_sensor::BinarySensor *binary_sensor);
 | 
			
		||||
  bool try_send_binary_sensor_state_(binary_sensor::BinarySensor *binary_sensor, bool state);
 | 
			
		||||
  bool try_send_binary_sensor_info_(binary_sensor::BinarySensor *binary_sensor);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  static bool try_send_binary_sensor_state(APIConnection *api, void *v_binary_sensor);
 | 
			
		||||
  static bool try_send_binary_sensor_state(APIConnection *api, binary_sensor::BinarySensor *binary_sensor, bool state);
 | 
			
		||||
  static bool try_send_binary_sensor_info(APIConnection *api, void *v_binary_sensor);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
  bool send_cover_state(cover::Cover *cover);
 | 
			
		||||
  void send_cover_info(cover::Cover *cover);
 | 
			
		||||
  static bool try_send_cover_state(APIConnection *api, void *v_cover);
 | 
			
		||||
  static bool try_send_cover_info(APIConnection *api, void *v_cover);
 | 
			
		||||
  void cover_command(const CoverCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_cover_state_(cover::Cover *cover);
 | 
			
		||||
  bool try_send_cover_info_(cover::Cover *cover);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_FAN
 | 
			
		||||
  bool send_fan_state(fan::Fan *fan);
 | 
			
		||||
  void send_fan_info(fan::Fan *fan);
 | 
			
		||||
  static bool try_send_fan_state(APIConnection *api, void *v_fan);
 | 
			
		||||
  static bool try_send_fan_info(APIConnection *api, void *v_fan);
 | 
			
		||||
  void fan_command(const FanCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_fan_state_(fan::Fan *fan);
 | 
			
		||||
  bool try_send_fan_info_(fan::Fan *fan);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LIGHT
 | 
			
		||||
  bool send_light_state(light::LightState *light);
 | 
			
		||||
  void send_light_info(light::LightState *light);
 | 
			
		||||
  static bool try_send_light_state(APIConnection *api, void *v_light);
 | 
			
		||||
  static bool try_send_light_info(APIConnection *api, void *v_light);
 | 
			
		||||
  void light_command(const LightCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_light_state_(light::LightState *light);
 | 
			
		||||
  bool try_send_light_info_(light::LightState *light);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  bool send_sensor_state(sensor::Sensor *sensor, float state);
 | 
			
		||||
  void send_sensor_info(sensor::Sensor *sensor);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_sensor_state_(sensor::Sensor *sensor);
 | 
			
		||||
  bool try_send_sensor_state_(sensor::Sensor *sensor, float state);
 | 
			
		||||
  bool try_send_sensor_info_(sensor::Sensor *sensor);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  static bool try_send_sensor_state(APIConnection *api, void *v_sensor);
 | 
			
		||||
  static bool try_send_sensor_state(APIConnection *api, sensor::Sensor *sensor, float state);
 | 
			
		||||
  static bool try_send_sensor_info(APIConnection *api, void *v_sensor);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  bool send_switch_state(switch_::Switch *a_switch, bool state);
 | 
			
		||||
  void send_switch_info(switch_::Switch *a_switch);
 | 
			
		||||
  static bool try_send_switch_state(APIConnection *api, void *v_a_switch);
 | 
			
		||||
  static bool try_send_switch_state(APIConnection *api, switch_::Switch *a_switch, bool state);
 | 
			
		||||
  static bool try_send_switch_info(APIConnection *api, void *v_a_switch);
 | 
			
		||||
  void switch_command(const SwitchCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_switch_state_(switch_::Switch *a_switch);
 | 
			
		||||
  bool try_send_switch_state_(switch_::Switch *a_switch, bool state);
 | 
			
		||||
  bool try_send_switch_info_(switch_::Switch *a_switch);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
 | 
			
		||||
  void send_text_sensor_info(text_sensor::TextSensor *text_sensor);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_text_sensor_state_(text_sensor::TextSensor *text_sensor);
 | 
			
		||||
  bool try_send_text_sensor_state_(text_sensor::TextSensor *text_sensor, std::string state);
 | 
			
		||||
  bool try_send_text_sensor_info_(text_sensor::TextSensor *text_sensor);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  static bool try_send_text_sensor_state(APIConnection *api, void *v_text_sensor);
 | 
			
		||||
  static bool try_send_text_sensor_state(APIConnection *api, text_sensor::TextSensor *text_sensor, std::string state);
 | 
			
		||||
  static bool try_send_text_sensor_info(APIConnection *api, void *v_text_sensor);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
  void set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
 | 
			
		||||
  void send_camera_info(esp32_camera::ESP32Camera *camera);
 | 
			
		||||
  static bool try_send_camera_info(APIConnection *api, void *v_camera);
 | 
			
		||||
  void camera_image(const CameraImageRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_camera_info_(esp32_camera::ESP32Camera *camera);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
  bool send_climate_state(climate::Climate *climate);
 | 
			
		||||
  void send_climate_info(climate::Climate *climate);
 | 
			
		||||
  static bool try_send_climate_state(APIConnection *api, void *v_climate);
 | 
			
		||||
  static bool try_send_climate_info(APIConnection *api, void *v_climate);
 | 
			
		||||
  void climate_command(const ClimateCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_climate_state_(climate::Climate *climate);
 | 
			
		||||
  bool try_send_climate_info_(climate::Climate *climate);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  bool send_number_state(number::Number *number, float state);
 | 
			
		||||
  void send_number_info(number::Number *number);
 | 
			
		||||
  static bool try_send_number_state(APIConnection *api, void *v_number);
 | 
			
		||||
  static bool try_send_number_state(APIConnection *api, number::Number *number, float state);
 | 
			
		||||
  static bool try_send_number_info(APIConnection *api, void *v_number);
 | 
			
		||||
  void number_command(const NumberCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_number_state_(number::Number *number);
 | 
			
		||||
  bool try_send_number_state_(number::Number *number, float state);
 | 
			
		||||
  bool try_send_number_info_(number::Number *number);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATE
 | 
			
		||||
  bool send_date_state(datetime::DateEntity *date);
 | 
			
		||||
  void send_date_info(datetime::DateEntity *date);
 | 
			
		||||
  static bool try_send_date_state(APIConnection *api, void *v_date);
 | 
			
		||||
  static bool try_send_date_info(APIConnection *api, void *v_date);
 | 
			
		||||
  void date_command(const DateCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_date_state_(datetime::DateEntity *date);
 | 
			
		||||
  bool try_send_date_info_(datetime::DateEntity *date);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
  bool send_time_state(datetime::TimeEntity *time);
 | 
			
		||||
  void send_time_info(datetime::TimeEntity *time);
 | 
			
		||||
  static bool try_send_time_state(APIConnection *api, void *v_time);
 | 
			
		||||
  static bool try_send_time_info(APIConnection *api, void *v_time);
 | 
			
		||||
  void time_command(const TimeCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_time_state_(datetime::TimeEntity *time);
 | 
			
		||||
  bool try_send_time_info_(datetime::TimeEntity *time);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  bool send_datetime_state(datetime::DateTimeEntity *datetime);
 | 
			
		||||
  void send_datetime_info(datetime::DateTimeEntity *datetime);
 | 
			
		||||
  static bool try_send_datetime_state(APIConnection *api, void *v_datetime);
 | 
			
		||||
  static bool try_send_datetime_info(APIConnection *api, void *v_datetime);
 | 
			
		||||
  void datetime_command(const DateTimeCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_datetime_state_(datetime::DateTimeEntity *datetime);
 | 
			
		||||
  bool try_send_datetime_info_(datetime::DateTimeEntity *datetime);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
  bool send_text_state(text::Text *text, std::string state);
 | 
			
		||||
  void send_text_info(text::Text *text);
 | 
			
		||||
  static bool try_send_text_state(APIConnection *api, void *v_text);
 | 
			
		||||
  static bool try_send_text_state(APIConnection *api, text::Text *text, std::string state);
 | 
			
		||||
  static bool try_send_text_info(APIConnection *api, void *v_text);
 | 
			
		||||
  void text_command(const TextCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_text_state_(text::Text *text);
 | 
			
		||||
  bool try_send_text_state_(text::Text *text, std::string state);
 | 
			
		||||
  bool try_send_text_info_(text::Text *text);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
  bool send_select_state(select::Select *select, std::string state);
 | 
			
		||||
  void send_select_info(select::Select *select);
 | 
			
		||||
  static bool try_send_select_state(APIConnection *api, void *v_select);
 | 
			
		||||
  static bool try_send_select_state(APIConnection *api, select::Select *select, std::string state);
 | 
			
		||||
  static bool try_send_select_info(APIConnection *api, void *v_select);
 | 
			
		||||
  void select_command(const SelectCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_select_state_(select::Select *select);
 | 
			
		||||
  bool try_send_select_state_(select::Select *select, std::string state);
 | 
			
		||||
  bool try_send_select_info_(select::Select *select);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  void send_button_info(button::Button *button);
 | 
			
		||||
  static bool try_send_button_info(APIConnection *api, void *v_button);
 | 
			
		||||
  void button_command(const ButtonCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_button_info_(button::Button *button);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
  bool send_lock_state(lock::Lock *a_lock, lock::LockState state);
 | 
			
		||||
  void send_lock_info(lock::Lock *a_lock);
 | 
			
		||||
  static bool try_send_lock_state(APIConnection *api, void *v_a_lock);
 | 
			
		||||
  static bool try_send_lock_state(APIConnection *api, lock::Lock *a_lock, lock::LockState state);
 | 
			
		||||
  static bool try_send_lock_info(APIConnection *api, void *v_a_lock);
 | 
			
		||||
  void lock_command(const LockCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_lock_state_(lock::Lock *a_lock);
 | 
			
		||||
  bool try_send_lock_state_(lock::Lock *a_lock, lock::LockState state);
 | 
			
		||||
  bool try_send_lock_info_(lock::Lock *a_lock);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
  bool send_valve_state(valve::Valve *valve);
 | 
			
		||||
  void send_valve_info(valve::Valve *valve);
 | 
			
		||||
  static bool try_send_valve_state(APIConnection *api, void *v_valve);
 | 
			
		||||
  static bool try_send_valve_info(APIConnection *api, void *v_valve);
 | 
			
		||||
  void valve_command(const ValveCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_valve_state_(valve::Valve *valve);
 | 
			
		||||
  bool try_send_valve_info_(valve::Valve *valve);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  bool send_media_player_state(media_player::MediaPlayer *media_player);
 | 
			
		||||
  void send_media_player_info(media_player::MediaPlayer *media_player);
 | 
			
		||||
  static bool try_send_media_player_state(APIConnection *api, void *v_media_player);
 | 
			
		||||
  static bool try_send_media_player_info(APIConnection *api, void *v_media_player);
 | 
			
		||||
  void media_player_command(const MediaPlayerCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_media_player_state_(media_player::MediaPlayer *media_player);
 | 
			
		||||
  bool try_send_media_player_info_(media_player::MediaPlayer *media_player);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
  bool try_send_log_message(int level, const char *tag, const char *line);
 | 
			
		||||
  void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
 | 
			
		||||
@@ -302,7 +221,6 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
 | 
			
		||||
  BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
 | 
			
		||||
      const SubscribeBluetoothConnectionsFreeRequest &msg) override;
 | 
			
		||||
  void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_HOMEASSISTANT_TIME
 | 
			
		||||
@@ -327,37 +245,25 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
  bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
			
		||||
  void send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
			
		||||
  static bool try_send_alarm_control_panel_state(APIConnection *api, void *v_a_alarm_control_panel);
 | 
			
		||||
  static bool try_send_alarm_control_panel_info(APIConnection *api, void *v_a_alarm_control_panel);
 | 
			
		||||
  void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_alarm_control_panel_state_(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
			
		||||
  bool try_send_alarm_control_panel_info_(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_EVENT
 | 
			
		||||
  void send_event(event::Event *event, std::string event_type);
 | 
			
		||||
  void send_event_info(event::Event *event);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_event_(event::Event *event);
 | 
			
		||||
  bool try_send_event_(event::Event *event, std::string event_type);
 | 
			
		||||
  bool try_send_event_info_(event::Event *event);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  static bool try_send_event(APIConnection *api, void *v_event);
 | 
			
		||||
  static bool try_send_event(APIConnection *api, event::Event *event, std::string event_type);
 | 
			
		||||
  static bool try_send_event_info(APIConnection *api, void *v_event);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  bool send_update_state(update::UpdateEntity *update);
 | 
			
		||||
  void send_update_info(update::UpdateEntity *update);
 | 
			
		||||
  static bool try_send_update_state(APIConnection *api, void *v_update);
 | 
			
		||||
  static bool try_send_update_info(APIConnection *api, void *v_update);
 | 
			
		||||
  void update_command(const UpdateCommandRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool try_send_update_state_(update::UpdateEntity *update);
 | 
			
		||||
  bool try_send_update_info_(update::UpdateEntity *update);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  void on_disconnect_response(const DisconnectResponse &value) override;
 | 
			
		||||
@@ -405,20 +311,11 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  void on_fatal_error() override;
 | 
			
		||||
  void on_unauthenticated_access() override;
 | 
			
		||||
  void on_no_setup_connection() override;
 | 
			
		||||
  ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
 | 
			
		||||
  ProtoWriteBuffer create_buffer() override {
 | 
			
		||||
    // FIXME: ensure no recursive writes can happen
 | 
			
		||||
    this->proto_write_buffer_.clear();
 | 
			
		||||
    // Get header padding size - used for both reserve and insert
 | 
			
		||||
    uint8_t header_padding = this->helper_->frame_header_padding();
 | 
			
		||||
    // Reserve space for header padding + message + footer
 | 
			
		||||
    // - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
 | 
			
		||||
    // - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
 | 
			
		||||
    this->proto_write_buffer_.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
 | 
			
		||||
    // Insert header padding bytes so message encoding starts at the correct position
 | 
			
		||||
    this->proto_write_buffer_.insert(this->proto_write_buffer_.begin(), header_padding, 0);
 | 
			
		||||
    return {&this->proto_write_buffer_};
 | 
			
		||||
  }
 | 
			
		||||
  bool try_to_clear_buffer(bool log_out_of_space);
 | 
			
		||||
  bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
 | 
			
		||||
 | 
			
		||||
  std::string get_client_combined_info() const { return this->client_combined_info_; }
 | 
			
		||||
@@ -426,99 +323,6 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
 protected:
 | 
			
		||||
  friend APIServer;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generic send entity state method to reduce code duplication.
 | 
			
		||||
   * Only attempts to build and send the message if the transmit buffer is available.
 | 
			
		||||
   *
 | 
			
		||||
   * This is the base version for entities that use their current state.
 | 
			
		||||
   *
 | 
			
		||||
   * @param entity The entity to send state for
 | 
			
		||||
   * @param try_send_func The function that tries to send the state
 | 
			
		||||
   * @return True on success or message deferred, false if subscription check failed
 | 
			
		||||
   */
 | 
			
		||||
  bool send_state_(esphome::EntityBase *entity, send_message_t try_send_func) {
 | 
			
		||||
    if (!this->state_subscription_)
 | 
			
		||||
      return false;
 | 
			
		||||
    if (this->try_to_clear_buffer(true) && (this->*try_send_func)(entity)) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    this->deferred_message_queue_.defer(entity, try_send_func);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Send entity state method that handles explicit state values.
 | 
			
		||||
   * Only attempts to build and send the message if the transmit buffer is available.
 | 
			
		||||
   *
 | 
			
		||||
   * This method accepts a state parameter to be used instead of the entity's current state.
 | 
			
		||||
   * It attempts to send the state with the provided value first, and if that fails due to buffer constraints,
 | 
			
		||||
   * it defers the entity for later processing using the entity-only function.
 | 
			
		||||
   *
 | 
			
		||||
   * @tparam EntityT The entity type
 | 
			
		||||
   * @tparam StateT Type of the state parameter
 | 
			
		||||
   * @tparam Args Additional argument types (if any)
 | 
			
		||||
   * @param entity The entity to send state for
 | 
			
		||||
   * @param try_send_entity_func The function that tries to send the state with entity pointer only
 | 
			
		||||
   * @param try_send_state_func The function that tries to send the state with entity and state parameters
 | 
			
		||||
   * @param state The state value to send
 | 
			
		||||
   * @param args Additional arguments to pass to the try_send_state_func
 | 
			
		||||
   * @return True on success or message deferred, false if subscription check failed
 | 
			
		||||
   */
 | 
			
		||||
  template<typename EntityT, typename StateT, typename... Args>
 | 
			
		||||
  bool send_state_with_value_(EntityT *entity, bool (APIConnection::*try_send_entity_func)(EntityT *),
 | 
			
		||||
                              bool (APIConnection::*try_send_state_func)(EntityT *, StateT, Args...), StateT state,
 | 
			
		||||
                              Args... args) {
 | 
			
		||||
    if (!this->state_subscription_)
 | 
			
		||||
      return false;
 | 
			
		||||
    if (this->try_to_clear_buffer(true) && (this->*try_send_state_func)(entity, state, args...)) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    this->deferred_message_queue_.defer(entity, reinterpret_cast<send_message_t>(try_send_entity_func));
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generic send entity info method to reduce code duplication.
 | 
			
		||||
   * Only attempts to build and send the message if the transmit buffer is available.
 | 
			
		||||
   *
 | 
			
		||||
   * @param entity The entity to send info for
 | 
			
		||||
   * @param try_send_func The function that tries to send the info
 | 
			
		||||
   */
 | 
			
		||||
  void send_info_(esphome::EntityBase *entity, send_message_t try_send_func) {
 | 
			
		||||
    if (this->try_to_clear_buffer(true) && (this->*try_send_func)(entity)) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this->deferred_message_queue_.defer(entity, try_send_func);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generic function for generating entity info response messages.
 | 
			
		||||
   * This is used to reduce duplication in the try_send_*_info functions.
 | 
			
		||||
   *
 | 
			
		||||
   * @param entity The entity to generate info for
 | 
			
		||||
   * @param response The response object
 | 
			
		||||
   * @param send_response_func Function pointer to send the response
 | 
			
		||||
   * @return True if the message was sent successfully
 | 
			
		||||
   */
 | 
			
		||||
  template<typename ResponseT>
 | 
			
		||||
  bool try_send_entity_info_(esphome::EntityBase *entity, ResponseT &response,
 | 
			
		||||
                             bool (APIServerConnectionBase::*send_response_func)(const ResponseT &)) {
 | 
			
		||||
    // Set common fields that are shared by all entity types
 | 
			
		||||
    response.key = entity->get_object_id_hash();
 | 
			
		||||
    response.object_id = entity->get_object_id();
 | 
			
		||||
 | 
			
		||||
    if (entity->has_own_name())
 | 
			
		||||
      response.name = entity->get_name();
 | 
			
		||||
 | 
			
		||||
    // Set common EntityBase properties
 | 
			
		||||
    response.icon = entity->get_icon();
 | 
			
		||||
    response.disabled_by_default = entity->is_disabled_by_default();
 | 
			
		||||
    response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
 | 
			
		||||
 | 
			
		||||
    // Send the response using the provided send method
 | 
			
		||||
    return (this->*send_response_func)(response);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool send_(const void *buf, size_t len, bool force);
 | 
			
		||||
 | 
			
		||||
  enum class ConnectionState {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "proto.h"
 | 
			
		||||
#include "api_pb2_size.h"
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -73,91 +72,6 @@ const char *api_error_to_str(APIError err) {
 | 
			
		||||
  return "UNKNOWN";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Common implementation for writing raw data to socket
 | 
			
		||||
template<typename StateEnum>
 | 
			
		||||
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket,
 | 
			
		||||
                                    std::vector<uint8_t> &tx_buf, const std::string &info, StateEnum &state,
 | 
			
		||||
                                    StateEnum failed_state) {
 | 
			
		||||
  // This method writes data to socket or buffers it
 | 
			
		||||
  // Returns APIError::OK if successful (or would block, but data has been buffered)
 | 
			
		||||
  // Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to failed_state
 | 
			
		||||
 | 
			
		||||
  if (iovcnt == 0)
 | 
			
		||||
    return APIError::OK;  // Nothing to do, success
 | 
			
		||||
 | 
			
		||||
  size_t total_write_len = 0;
 | 
			
		||||
  for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
#ifdef HELPER_LOG_PACKETS
 | 
			
		||||
    ESP_LOGVV(TAG, "Sending raw: %s",
 | 
			
		||||
              format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
 | 
			
		||||
#endif
 | 
			
		||||
    total_write_len += iov[i].iov_len;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf.empty()) {
 | 
			
		||||
    // try to empty tx_buf first
 | 
			
		||||
    while (!tx_buf.empty()) {
 | 
			
		||||
      ssize_t sent = socket->write(tx_buf.data(), tx_buf.size());
 | 
			
		||||
      if (is_would_block(sent)) {
 | 
			
		||||
        break;
 | 
			
		||||
      } else if (sent == -1) {
 | 
			
		||||
        ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
 | 
			
		||||
        state = failed_state;
 | 
			
		||||
        return APIError::SOCKET_WRITE_FAILED;  // Socket write failed
 | 
			
		||||
      }
 | 
			
		||||
      // TODO: inefficient if multiple packets in txbuf
 | 
			
		||||
      // replace with deque of buffers
 | 
			
		||||
      tx_buf.erase(tx_buf.begin(), tx_buf.begin() + sent);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf.empty()) {
 | 
			
		||||
    // tx buf not empty, can't write now because then stream would be inconsistent
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf.reserve(tx_buf.size() + total_write_len);
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                    reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;  // Success, data buffered
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ssize_t sent = socket->writev(iov, iovcnt);
 | 
			
		||||
  if (is_would_block(sent)) {
 | 
			
		||||
    // operation would block, add buffer to tx_buf
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf.reserve(tx_buf.size() + total_write_len);
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                    reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;  // Success, data buffered
 | 
			
		||||
  } else if (sent == -1) {
 | 
			
		||||
    // an error occurred
 | 
			
		||||
    ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
 | 
			
		||||
    state = failed_state;
 | 
			
		||||
    return APIError::SOCKET_WRITE_FAILED;  // Socket write failed
 | 
			
		||||
  } else if ((size_t) sent != total_write_len) {
 | 
			
		||||
    // partially sent, add end to tx_buf
 | 
			
		||||
    size_t remaining = total_write_len - sent;
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf.reserve(tx_buf.size() + remaining);
 | 
			
		||||
 | 
			
		||||
    size_t to_consume = sent;
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      if (to_consume >= iov[i].iov_len) {
 | 
			
		||||
        to_consume -= iov[i].iov_len;
 | 
			
		||||
      } else {
 | 
			
		||||
        tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
 | 
			
		||||
                      reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
        to_consume = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;  // Success, data buffered
 | 
			
		||||
  }
 | 
			
		||||
  return APIError::OK;  // Success, all data sent
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
 | 
			
		||||
// uncomment to log raw packets
 | 
			
		||||
//#define HELPER_LOG_PACKETS
 | 
			
		||||
@@ -493,12 +407,9 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &rea
 | 
			
		||||
  std::vector<uint8_t> data;
 | 
			
		||||
  data.resize(reason.length() + 1);
 | 
			
		||||
  data[0] = 0x01;  // failure
 | 
			
		||||
 | 
			
		||||
  // Copy error message in bulk
 | 
			
		||||
  if (!reason.empty()) {
 | 
			
		||||
    std::memcpy(data.data() + 1, reason.c_str(), reason.length());
 | 
			
		||||
  for (size_t i = 0; i < reason.length(); i++) {
 | 
			
		||||
    data[i + 1] = (uint8_t) reason[i];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // temporarily remove failed state
 | 
			
		||||
  auto orig_state = state_;
 | 
			
		||||
  state_ = State::EXPLICIT_REJECT;
 | 
			
		||||
@@ -560,7 +471,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
			
		||||
  return APIError::OK;
 | 
			
		||||
}
 | 
			
		||||
bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
 | 
			
		||||
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
 | 
			
		||||
APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
 | 
			
		||||
  int err;
 | 
			
		||||
  APIError aerr;
 | 
			
		||||
  aerr = state_action_();
 | 
			
		||||
@@ -572,36 +483,31 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
 | 
			
		||||
    return APIError::WOULD_BLOCK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
 | 
			
		||||
  // Message data starts after padding
 | 
			
		||||
  size_t payload_len = raw_buffer->size() - frame_header_padding_;
 | 
			
		||||
  size_t padding = 0;
 | 
			
		||||
  size_t msg_len = 4 + payload_len + padding;
 | 
			
		||||
  size_t frame_len = 3 + msg_len + noise_cipherstate_get_mac_length(send_cipher_);
 | 
			
		||||
  auto tmpbuf = std::unique_ptr<uint8_t[]>{new (std::nothrow) uint8_t[frame_len]};
 | 
			
		||||
  if (tmpbuf == nullptr) {
 | 
			
		||||
    HELPER_LOG("Could not allocate for writing packet");
 | 
			
		||||
    return APIError::OUT_OF_MEMORY;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // We need to resize to include MAC space, but we already reserved it in create_buffer
 | 
			
		||||
  raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
 | 
			
		||||
 | 
			
		||||
  // Write the noise header in the padded area
 | 
			
		||||
  // Buffer layout:
 | 
			
		||||
  // [0]    - 0x01 indicator byte
 | 
			
		||||
  // [1-2]  - Size of encrypted payload (filled after encryption)
 | 
			
		||||
  // [3-4]  - Message type (encrypted)
 | 
			
		||||
  // [5-6]  - Payload length (encrypted)
 | 
			
		||||
  // [7...] - Actual payload data (encrypted)
 | 
			
		||||
  uint8_t *buf_start = raw_buffer->data();
 | 
			
		||||
  buf_start[0] = 0x01;  // indicator
 | 
			
		||||
  // buf_start[1], buf_start[2] to be set later after encryption
 | 
			
		||||
  tmpbuf[0] = 0x01;  // indicator
 | 
			
		||||
  // tmpbuf[1], tmpbuf[2] to be set later
 | 
			
		||||
  const uint8_t msg_offset = 3;
 | 
			
		||||
  buf_start[msg_offset + 0] = (uint8_t) (type >> 8);         // type high byte
 | 
			
		||||
  buf_start[msg_offset + 1] = (uint8_t) type;                // type low byte
 | 
			
		||||
  buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8);  // data_len high byte
 | 
			
		||||
  buf_start[msg_offset + 3] = (uint8_t) payload_len;         // data_len low byte
 | 
			
		||||
  // payload data is already in the buffer starting at position 7
 | 
			
		||||
  const uint8_t payload_offset = msg_offset + 4;
 | 
			
		||||
  tmpbuf[msg_offset + 0] = (uint8_t) (type >> 8);  // type
 | 
			
		||||
  tmpbuf[msg_offset + 1] = (uint8_t) type;
 | 
			
		||||
  tmpbuf[msg_offset + 2] = (uint8_t) (payload_len >> 8);  // data_len
 | 
			
		||||
  tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
 | 
			
		||||
  // copy data
 | 
			
		||||
  std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
 | 
			
		||||
  // fill padding with zeros
 | 
			
		||||
  std::fill(&tmpbuf[payload_offset + payload_len], &tmpbuf[frame_len], 0);
 | 
			
		||||
 | 
			
		||||
  NoiseBuffer mbuf;
 | 
			
		||||
  noise_buffer_init(mbuf);
 | 
			
		||||
  // The capacity parameter should be msg_len + frame_footer_size_ (MAC length) to allow space for encryption
 | 
			
		||||
  noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
 | 
			
		||||
  noise_buffer_set_inout(mbuf, &tmpbuf[msg_offset], msg_len, frame_len - msg_offset);
 | 
			
		||||
  err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
 | 
			
		||||
  if (err != 0) {
 | 
			
		||||
    state_ = State::FAILED;
 | 
			
		||||
@@ -610,13 +516,11 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  size_t total_len = 3 + mbuf.size;
 | 
			
		||||
  buf_start[1] = (uint8_t) (mbuf.size >> 8);
 | 
			
		||||
  buf_start[2] = (uint8_t) mbuf.size;
 | 
			
		||||
  tmpbuf[1] = (uint8_t) (mbuf.size >> 8);
 | 
			
		||||
  tmpbuf[2] = (uint8_t) mbuf.size;
 | 
			
		||||
 | 
			
		||||
  struct iovec iov;
 | 
			
		||||
  // Point iov_base to the beginning of the buffer (no unused padding in Noise)
 | 
			
		||||
  // We send the entire frame: indicator + size + encrypted(type + data_len + payload + MAC)
 | 
			
		||||
  iov.iov_base = buf_start;
 | 
			
		||||
  iov.iov_base = &tmpbuf[0];
 | 
			
		||||
  iov.iov_len = total_len;
 | 
			
		||||
 | 
			
		||||
  // write raw to not have two packets sent if NAGLE disabled
 | 
			
		||||
@@ -642,6 +546,71 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() {
 | 
			
		||||
 | 
			
		||||
  return APIError::OK;
 | 
			
		||||
}
 | 
			
		||||
/** Write the data to the socket, or buffer it a write would block
 | 
			
		||||
 *
 | 
			
		||||
 * @param data The data to write
 | 
			
		||||
 * @param len The length of data
 | 
			
		||||
 */
 | 
			
		||||
APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
  if (iovcnt == 0)
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  APIError aerr;
 | 
			
		||||
 | 
			
		||||
  size_t total_write_len = 0;
 | 
			
		||||
  for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
#ifdef HELPER_LOG_PACKETS
 | 
			
		||||
    ESP_LOGVV(TAG, "Sending raw: %s",
 | 
			
		||||
              format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
 | 
			
		||||
#endif
 | 
			
		||||
    total_write_len += iov[i].iov_len;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf_.empty()) {
 | 
			
		||||
    // try to empty tx_buf_ first
 | 
			
		||||
    aerr = try_send_tx_buf_();
 | 
			
		||||
    if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
 | 
			
		||||
      return aerr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf_.empty()) {
 | 
			
		||||
    // tx buf not empty, can't write now because then stream would be inconsistent
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ssize_t sent = socket_->writev(iov, iovcnt);
 | 
			
		||||
  if (is_would_block(sent)) {
 | 
			
		||||
    // operation would block, add buffer to tx_buf
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  } else if (sent == -1) {
 | 
			
		||||
    // an error occurred
 | 
			
		||||
    state_ = State::FAILED;
 | 
			
		||||
    HELPER_LOG("Socket write failed with errno %d", errno);
 | 
			
		||||
    return APIError::SOCKET_WRITE_FAILED;
 | 
			
		||||
  } else if ((size_t) sent != total_write_len) {
 | 
			
		||||
    // partially sent, add end to tx_buf
 | 
			
		||||
    size_t to_consume = sent;
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      if (to_consume >= iov[i].iov_len) {
 | 
			
		||||
        to_consume -= iov[i].iov_len;
 | 
			
		||||
      } else {
 | 
			
		||||
        tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
 | 
			
		||||
                       reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
        to_consume = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  }
 | 
			
		||||
  // fully sent
 | 
			
		||||
  return APIError::OK;
 | 
			
		||||
}
 | 
			
		||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
 | 
			
		||||
  uint8_t header[3];
 | 
			
		||||
  header[0] = 0x01;  // indicator
 | 
			
		||||
@@ -728,8 +697,6 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
 | 
			
		||||
    return APIError::HANDSHAKESTATE_SPLIT_FAILED;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
 | 
			
		||||
 | 
			
		||||
  HELPER_LOG("Handshake complete!");
 | 
			
		||||
  noise_handshakestate_free(handshake_);
 | 
			
		||||
  handshake_ = nullptr;
 | 
			
		||||
@@ -777,11 +744,6 @@ void noise_rand_bytes(void *output, size_t len) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Explicit template instantiation for Noise
 | 
			
		||||
template APIError APIFrameHelper::write_raw_<APINoiseFrameHelper::State>(
 | 
			
		||||
    const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
 | 
			
		||||
    APINoiseFrameHelper::State &state, APINoiseFrameHelper::State failed_state);
 | 
			
		||||
#endif  // USE_API_NOISE
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_PLAINTEXT
 | 
			
		||||
@@ -842,10 +804,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
 | 
			
		||||
  // read header
 | 
			
		||||
  while (!rx_header_parsed_) {
 | 
			
		||||
    uint8_t data;
 | 
			
		||||
    // Reading one byte at a time is fastest in practice for ESP32 when
 | 
			
		||||
    // there is no data on the wire (which is the common case).
 | 
			
		||||
    // This results in faster failure detection compared to
 | 
			
		||||
    // attempting to read multiple bytes at once.
 | 
			
		||||
    ssize_t received = socket_->read(&data, 1);
 | 
			
		||||
    if (received == -1) {
 | 
			
		||||
      if (errno == EWOULDBLOCK || errno == EAGAIN) {
 | 
			
		||||
@@ -859,60 +817,27 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
 | 
			
		||||
      HELPER_LOG("Connection closed");
 | 
			
		||||
      return APIError::CONNECTION_CLOSED;
 | 
			
		||||
    }
 | 
			
		||||
    rx_header_buf_.push_back(data);
 | 
			
		||||
 | 
			
		||||
    // Successfully read a byte
 | 
			
		||||
 | 
			
		||||
    // Process byte according to current buffer position
 | 
			
		||||
    if (rx_header_buf_pos_ == 0) {  // Case 1: First byte (indicator byte)
 | 
			
		||||
      if (data != 0x00) {
 | 
			
		||||
    // try parse header
 | 
			
		||||
    if (rx_header_buf_[0] != 0x00) {
 | 
			
		||||
      state_ = State::FAILED;
 | 
			
		||||
        HELPER_LOG("Bad indicator byte %u", data);
 | 
			
		||||
      HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
 | 
			
		||||
      return APIError::BAD_INDICATOR;
 | 
			
		||||
    }
 | 
			
		||||
      // We don't store the indicator byte, just increment position
 | 
			
		||||
      rx_header_buf_pos_ = 1;  // Set to 1 directly
 | 
			
		||||
      continue;                // Need more bytes before we can parse
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check buffer overflow before storing
 | 
			
		||||
    if (rx_header_buf_pos_ == 5) {  // Case 2: Buffer would overflow (5 bytes is max allowed)
 | 
			
		||||
      state_ = State::FAILED;
 | 
			
		||||
      HELPER_LOG("Header buffer overflow");
 | 
			
		||||
      return APIError::BAD_DATA_PACKET;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Store byte in buffer (adjust index to account for skipped indicator byte)
 | 
			
		||||
    rx_header_buf_[rx_header_buf_pos_ - 1] = data;
 | 
			
		||||
 | 
			
		||||
    // Increment position after storing
 | 
			
		||||
    rx_header_buf_pos_++;
 | 
			
		||||
 | 
			
		||||
    // Case 3: If we only have one varint byte, we need more
 | 
			
		||||
    if (rx_header_buf_pos_ == 2) {  // Have read indicator + 1 byte
 | 
			
		||||
      continue;                     // Need more bytes before we can parse
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // At this point, we have at least 3 bytes total:
 | 
			
		||||
    //   - Validated indicator byte (0x00) but not stored
 | 
			
		||||
    //   - At least 2 bytes in the buffer for the varints
 | 
			
		||||
    // Buffer layout:
 | 
			
		||||
    //   First 1-3 bytes: Message size varint (variable length)
 | 
			
		||||
    //     - 2 bytes would only allow up to 16383, which is less than noise's 65535
 | 
			
		||||
    //     - 3 bytes allows up to 2097151, ensuring we support at least as much as noise
 | 
			
		||||
    //   Remaining 1-2 bytes: Message type varint (variable length)
 | 
			
		||||
    // We now attempt to parse both varints. If either is incomplete,
 | 
			
		||||
    // we'll continue reading more bytes.
 | 
			
		||||
 | 
			
		||||
    size_t i = 1;
 | 
			
		||||
    uint32_t consumed = 0;
 | 
			
		||||
    auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[0], rx_header_buf_pos_ - 1, &consumed);
 | 
			
		||||
    auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
 | 
			
		||||
    if (!msg_size_varint.has_value()) {
 | 
			
		||||
      // not enough data there yet
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    i += consumed;
 | 
			
		||||
    rx_header_parsed_len_ = msg_size_varint->as_uint32();
 | 
			
		||||
 | 
			
		||||
    auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[consumed], rx_header_buf_pos_ - 1 - consumed, &consumed);
 | 
			
		||||
    auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
 | 
			
		||||
    if (!msg_type_varint.has_value()) {
 | 
			
		||||
      // not enough data there yet
 | 
			
		||||
      continue;
 | 
			
		||||
@@ -958,7 +883,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
 | 
			
		||||
  // consume msg
 | 
			
		||||
  rx_buf_ = {};
 | 
			
		||||
  rx_buf_len_ = 0;
 | 
			
		||||
  rx_header_buf_pos_ = 0;
 | 
			
		||||
  rx_header_buf_.clear();
 | 
			
		||||
  rx_header_parsed_ = false;
 | 
			
		||||
  return APIError::OK;
 | 
			
		||||
}
 | 
			
		||||
@@ -1002,66 +927,26 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
			
		||||
  return APIError::OK;
 | 
			
		||||
}
 | 
			
		||||
bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
 | 
			
		||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
 | 
			
		||||
APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
 | 
			
		||||
  if (state_ != State::DATA) {
 | 
			
		||||
    return APIError::BAD_STATE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
 | 
			
		||||
  // Message data starts after padding (frame_header_padding_ = 6)
 | 
			
		||||
  size_t payload_len = raw_buffer->size() - frame_header_padding_;
 | 
			
		||||
  std::vector<uint8_t> header;
 | 
			
		||||
  header.push_back(0x00);
 | 
			
		||||
  ProtoVarInt(payload_len).encode(header);
 | 
			
		||||
  ProtoVarInt(type).encode(header);
 | 
			
		||||
 | 
			
		||||
  // Calculate varint sizes for header components
 | 
			
		||||
  size_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
 | 
			
		||||
  size_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
 | 
			
		||||
  size_t total_header_len = 1 + size_varint_len + type_varint_len;
 | 
			
		||||
 | 
			
		||||
  if (total_header_len > frame_header_padding_) {
 | 
			
		||||
    // Header is too large to fit in the padding
 | 
			
		||||
    return APIError::BAD_ARG;
 | 
			
		||||
  struct iovec iov[2];
 | 
			
		||||
  iov[0].iov_base = &header[0];
 | 
			
		||||
  iov[0].iov_len = header.size();
 | 
			
		||||
  if (payload_len == 0) {
 | 
			
		||||
    return write_raw_(iov, 1);
 | 
			
		||||
  }
 | 
			
		||||
  iov[1].iov_base = const_cast<uint8_t *>(payload);
 | 
			
		||||
  iov[1].iov_len = payload_len;
 | 
			
		||||
 | 
			
		||||
  // Calculate where to start writing the header
 | 
			
		||||
  // The header starts at the latest possible position to minimize unused padding
 | 
			
		||||
  //
 | 
			
		||||
  // Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
 | 
			
		||||
  // [0-2]  - Unused padding
 | 
			
		||||
  // [3]    - 0x00 indicator byte
 | 
			
		||||
  // [4]    - Payload size varint (1 byte, for sizes 0-127)
 | 
			
		||||
  // [5]    - Message type varint (1 byte, for types 0-127)
 | 
			
		||||
  // [6...] - Actual payload data
 | 
			
		||||
  //
 | 
			
		||||
  // Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
 | 
			
		||||
  // [0-1]  - Unused padding
 | 
			
		||||
  // [2]    - 0x00 indicator byte
 | 
			
		||||
  // [3-4]  - Payload size varint (2 bytes, for sizes 128-16383)
 | 
			
		||||
  // [5]    - Message type varint (1 byte, for types 0-127)
 | 
			
		||||
  // [6...] - Actual payload data
 | 
			
		||||
  //
 | 
			
		||||
  // Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
 | 
			
		||||
  // [0]    - 0x00 indicator byte
 | 
			
		||||
  // [1-3]  - Payload size varint (3 bytes, for sizes 16384-2097151)
 | 
			
		||||
  // [4-5]  - Message type varint (2 bytes, for types 128-32767)
 | 
			
		||||
  // [6...] - Actual payload data
 | 
			
		||||
  uint8_t *buf_start = raw_buffer->data();
 | 
			
		||||
  size_t header_offset = frame_header_padding_ - total_header_len;
 | 
			
		||||
 | 
			
		||||
  // Write the plaintext header
 | 
			
		||||
  buf_start[header_offset] = 0x00;  // indicator
 | 
			
		||||
 | 
			
		||||
  // Encode size varint directly into buffer
 | 
			
		||||
  ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
 | 
			
		||||
 | 
			
		||||
  // Encode type varint directly into buffer
 | 
			
		||||
  ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
 | 
			
		||||
 | 
			
		||||
  struct iovec iov;
 | 
			
		||||
  // Point iov_base to the beginning of our header (skip unused padding)
 | 
			
		||||
  // This ensures we only send the actual header and payload, not the empty padding bytes
 | 
			
		||||
  iov.iov_base = buf_start + header_offset;
 | 
			
		||||
  iov.iov_len = total_header_len + payload_len;
 | 
			
		||||
 | 
			
		||||
  return write_raw_(&iov, 1);
 | 
			
		||||
  return write_raw_(iov, 2);
 | 
			
		||||
}
 | 
			
		||||
APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
 | 
			
		||||
  // try send from tx_buf
 | 
			
		||||
@@ -1081,6 +966,71 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
 | 
			
		||||
 | 
			
		||||
  return APIError::OK;
 | 
			
		||||
}
 | 
			
		||||
/** Write the data to the socket, or buffer it a write would block
 | 
			
		||||
 *
 | 
			
		||||
 * @param data The data to write
 | 
			
		||||
 * @param len The length of data
 | 
			
		||||
 */
 | 
			
		||||
APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
  if (iovcnt == 0)
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  APIError aerr;
 | 
			
		||||
 | 
			
		||||
  size_t total_write_len = 0;
 | 
			
		||||
  for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
#ifdef HELPER_LOG_PACKETS
 | 
			
		||||
    ESP_LOGVV(TAG, "Sending raw: %s",
 | 
			
		||||
              format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
 | 
			
		||||
#endif
 | 
			
		||||
    total_write_len += iov[i].iov_len;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf_.empty()) {
 | 
			
		||||
    // try to empty tx_buf_ first
 | 
			
		||||
    aerr = try_send_tx_buf_();
 | 
			
		||||
    if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
 | 
			
		||||
      return aerr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf_.empty()) {
 | 
			
		||||
    // tx buf not empty, can't write now because then stream would be inconsistent
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ssize_t sent = socket_->writev(iov, iovcnt);
 | 
			
		||||
  if (is_would_block(sent)) {
 | 
			
		||||
    // operation would block, add buffer to tx_buf
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  } else if (sent == -1) {
 | 
			
		||||
    // an error occurred
 | 
			
		||||
    state_ = State::FAILED;
 | 
			
		||||
    HELPER_LOG("Socket write failed with errno %d", errno);
 | 
			
		||||
    return APIError::SOCKET_WRITE_FAILED;
 | 
			
		||||
  } else if ((size_t) sent != total_write_len) {
 | 
			
		||||
    // partially sent, add end to tx_buf
 | 
			
		||||
    size_t to_consume = sent;
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      if (to_consume >= iov[i].iov_len) {
 | 
			
		||||
        to_consume -= iov[i].iov_len;
 | 
			
		||||
      } else {
 | 
			
		||||
        tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
 | 
			
		||||
                       reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
        to_consume = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  }
 | 
			
		||||
  // fully sent
 | 
			
		||||
  return APIError::OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APIError APIPlaintextFrameHelper::close() {
 | 
			
		||||
  state_ = State::CLOSED;
 | 
			
		||||
@@ -1098,11 +1048,6 @@ APIError APIPlaintextFrameHelper::shutdown(int how) {
 | 
			
		||||
  }
 | 
			
		||||
  return APIError::OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Explicit template instantiation for Plaintext
 | 
			
		||||
template APIError APIFrameHelper::write_raw_<APIPlaintextFrameHelper::State>(
 | 
			
		||||
    const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
 | 
			
		||||
    APIPlaintextFrameHelper::State &state, APIPlaintextFrameHelper::State failed_state);
 | 
			
		||||
#endif  // USE_API_PLAINTEXT
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,6 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
class ProtoWriteBuffer;
 | 
			
		||||
 | 
			
		||||
struct ReadPacketBuffer {
 | 
			
		||||
  std::vector<uint8_t> container;
 | 
			
		||||
  uint16_t type;
 | 
			
		||||
@@ -67,46 +65,26 @@ class APIFrameHelper {
 | 
			
		||||
  virtual APIError loop() = 0;
 | 
			
		||||
  virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
 | 
			
		||||
  virtual bool can_write_without_blocking() = 0;
 | 
			
		||||
  virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0;
 | 
			
		||||
  virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
 | 
			
		||||
  virtual std::string getpeername() = 0;
 | 
			
		||||
  virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
 | 
			
		||||
  virtual APIError close() = 0;
 | 
			
		||||
  virtual APIError shutdown(int how) = 0;
 | 
			
		||||
  // Give this helper a name for logging
 | 
			
		||||
  virtual void set_log_info(std::string info) = 0;
 | 
			
		||||
  // Get the frame header padding required by this protocol
 | 
			
		||||
  virtual uint8_t frame_header_padding() = 0;
 | 
			
		||||
  // Get the frame footer size required by this protocol
 | 
			
		||||
  virtual uint8_t frame_footer_size() = 0;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  // Common implementation for writing raw data to socket
 | 
			
		||||
  template<typename StateEnum>
 | 
			
		||||
  APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
 | 
			
		||||
                      const std::string &info, StateEnum &state, StateEnum failed_state);
 | 
			
		||||
 | 
			
		||||
  uint8_t frame_header_padding_{0};
 | 
			
		||||
  uint8_t frame_footer_size_{0};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
class APINoiseFrameHelper : public APIFrameHelper {
 | 
			
		||||
 public:
 | 
			
		||||
  APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
 | 
			
		||||
      : socket_(std::move(socket)), ctx_(std::move(ctx)) {
 | 
			
		||||
    // Noise header structure:
 | 
			
		||||
    // Pos 0: indicator (0x01)
 | 
			
		||||
    // Pos 1-2: encrypted payload size (16-bit big-endian)
 | 
			
		||||
    // Pos 3-6: encrypted type (16-bit) + data_len (16-bit)
 | 
			
		||||
    // Pos 7+: actual payload data
 | 
			
		||||
    frame_header_padding_ = 7;
 | 
			
		||||
  }
 | 
			
		||||
      : socket_(std::move(socket)), ctx_(std::move(std::move(ctx))) {}
 | 
			
		||||
  ~APINoiseFrameHelper() override;
 | 
			
		||||
  APIError init() override;
 | 
			
		||||
  APIError loop() override;
 | 
			
		||||
  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
			
		||||
  bool can_write_without_blocking() override;
 | 
			
		||||
  APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
 | 
			
		||||
  APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
 | 
			
		||||
  std::string getpeername() override { return this->socket_->getpeername(); }
 | 
			
		||||
  int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
 | 
			
		||||
    return this->socket_->getpeername(addr, addrlen);
 | 
			
		||||
@@ -115,10 +93,6 @@ class APINoiseFrameHelper : public APIFrameHelper {
 | 
			
		||||
  APIError shutdown(int how) override;
 | 
			
		||||
  // Give this helper a name for logging
 | 
			
		||||
  void set_log_info(std::string info) override { info_ = std::move(info); }
 | 
			
		||||
  // Get the frame header padding required by this protocol
 | 
			
		||||
  uint8_t frame_header_padding() override { return frame_header_padding_; }
 | 
			
		||||
  // Get the frame footer size required by this protocol
 | 
			
		||||
  uint8_t frame_footer_size() override { return frame_footer_size_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  struct ParsedFrame {
 | 
			
		||||
@@ -129,9 +103,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
 | 
			
		||||
  APIError try_read_frame_(ParsedFrame *frame);
 | 
			
		||||
  APIError try_send_tx_buf_();
 | 
			
		||||
  APIError write_frame_(const uint8_t *data, size_t len);
 | 
			
		||||
  inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
    return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
 | 
			
		||||
  }
 | 
			
		||||
  APIError write_raw_(const struct iovec *iov, int iovcnt);
 | 
			
		||||
  APIError init_handshake_();
 | 
			
		||||
  APIError check_handshake_finished_();
 | 
			
		||||
  void send_explicit_handshake_reject_(const std::string &reason);
 | 
			
		||||
@@ -139,9 +111,6 @@ class APINoiseFrameHelper : public APIFrameHelper {
 | 
			
		||||
  std::unique_ptr<socket::Socket> socket_;
 | 
			
		||||
 | 
			
		||||
  std::string info_;
 | 
			
		||||
  // Fixed-size header buffer for noise protocol:
 | 
			
		||||
  // 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
 | 
			
		||||
  // Note: Maximum message size is 65535, with a limit of 128 bytes during handshake phase
 | 
			
		||||
  uint8_t rx_header_buf_[3];
 | 
			
		||||
  size_t rx_header_buf_len_ = 0;
 | 
			
		||||
  std::vector<uint8_t> rx_buf_;
 | 
			
		||||
@@ -172,20 +141,13 @@ class APINoiseFrameHelper : public APIFrameHelper {
 | 
			
		||||
#ifdef USE_API_PLAINTEXT
 | 
			
		||||
class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
			
		||||
 public:
 | 
			
		||||
  APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {
 | 
			
		||||
    // Plaintext header structure (worst case):
 | 
			
		||||
    // Pos 0: indicator (0x00)
 | 
			
		||||
    // Pos 1-3: payload size varint (up to 3 bytes)
 | 
			
		||||
    // Pos 4-5: message type varint (up to 2 bytes)
 | 
			
		||||
    // Pos 6+: actual payload data
 | 
			
		||||
    frame_header_padding_ = 6;
 | 
			
		||||
  }
 | 
			
		||||
  APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
 | 
			
		||||
  ~APIPlaintextFrameHelper() override = default;
 | 
			
		||||
  APIError init() override;
 | 
			
		||||
  APIError loop() override;
 | 
			
		||||
  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
			
		||||
  bool can_write_without_blocking() override;
 | 
			
		||||
  APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
 | 
			
		||||
  APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
 | 
			
		||||
  std::string getpeername() override { return this->socket_->getpeername(); }
 | 
			
		||||
  int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
 | 
			
		||||
    return this->socket_->getpeername(addr, addrlen);
 | 
			
		||||
@@ -194,10 +156,6 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
			
		||||
  APIError shutdown(int how) override;
 | 
			
		||||
  // Give this helper a name for logging
 | 
			
		||||
  void set_log_info(std::string info) override { info_ = std::move(info); }
 | 
			
		||||
  // Get the frame header padding required by this protocol
 | 
			
		||||
  uint8_t frame_header_padding() override { return frame_header_padding_; }
 | 
			
		||||
  // Get the frame footer size required by this protocol
 | 
			
		||||
  uint8_t frame_footer_size() override { return frame_footer_size_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  struct ParsedFrame {
 | 
			
		||||
@@ -206,23 +164,12 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
			
		||||
 | 
			
		||||
  APIError try_read_frame_(ParsedFrame *frame);
 | 
			
		||||
  APIError try_send_tx_buf_();
 | 
			
		||||
  inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
    return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
 | 
			
		||||
  }
 | 
			
		||||
  APIError write_raw_(const struct iovec *iov, int iovcnt);
 | 
			
		||||
 | 
			
		||||
  std::unique_ptr<socket::Socket> socket_;
 | 
			
		||||
 | 
			
		||||
  std::string info_;
 | 
			
		||||
  // Fixed-size header buffer for plaintext protocol:
 | 
			
		||||
  // We only need space for the two varints since we validate the indicator byte separately.
 | 
			
		||||
  // To match noise protocol's maximum message size (65535), we need:
 | 
			
		||||
  // 3 bytes for message size varint (supports up to 2097151) + 2 bytes for message type varint
 | 
			
		||||
  //
 | 
			
		||||
  // While varints could theoretically be up to 10 bytes each for 64-bit values,
 | 
			
		||||
  // attempting to process messages with headers that large would likely crash the
 | 
			
		||||
  // ESP32 due to memory constraints.
 | 
			
		||||
  uint8_t rx_header_buf_[5];  // 5 bytes for varints (3 for size + 2 for type)
 | 
			
		||||
  uint8_t rx_header_buf_pos_ = 0;
 | 
			
		||||
  std::vector<uint8_t> rx_header_buf_;
 | 
			
		||||
  bool rx_header_parsed_ = false;
 | 
			
		||||
  uint32_t rx_header_parsed_type_ = 0;
 | 
			
		||||
  uint32_t rx_header_parsed_len_ = 0;
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
// This file was automatically generated with a tool.
 | 
			
		||||
// See script/api_protobuf/api_protobuf.py
 | 
			
		||||
// See scripts/api_protobuf/api_protobuf.py
 | 
			
		||||
#include "api_pb2_service.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
@@ -292,24 +292,6 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
bool APIServerConnectionBase::send_list_entities_siren_response(const ListEntitiesSirenResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_list_entities_siren_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<ListEntitiesSirenResponse>(msg, 55);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
bool APIServerConnectionBase::send_siren_state_response(const SirenStateResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_siren_state_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<SirenStateResponse>(msg, 56);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
bool APIServerConnectionBase::send_list_entities_lock_response(const ListEntitiesLockResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
@@ -490,16 +472,6 @@ bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const B
 | 
			
		||||
  return this->send_message_<BluetoothDeviceClearCacheResponse>(msg, 88);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_scanner_state_response(const BluetoothScannerStateResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_scanner_state_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothScannerStateResponse>(msg, 126);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
@@ -921,17 +893,6 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
			
		||||
      ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_select_command_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 57: {
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
      SirenCommandRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_siren_command_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_siren_command_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -1251,17 +1212,6 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
			
		||||
      ESP_LOGVV(TAG, "on_noise_encryption_set_key_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_noise_encryption_set_key_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 127: {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
      BluetoothScannerSetModeRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_bluetooth_scanner_set_mode_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_bluetooth_scanner_set_mode_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -1398,45 +1348,6 @@ void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncrypt
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->button_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->camera_image(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->climate_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
@@ -1450,32 +1361,6 @@ void APIServerConnection::on_cover_command_request(const CoverCommandRequest &ms
 | 
			
		||||
  this->cover_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATE
 | 
			
		||||
void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->date_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->datetime_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_FAN
 | 
			
		||||
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
@@ -1502,8 +1387,8 @@ void APIServerConnection::on_light_command_request(const LightCommandRequest &ms
 | 
			
		||||
  this->light_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
@@ -1512,11 +1397,11 @@ void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg)
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->lock_command(msg);
 | 
			
		||||
  this->switch_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
@@ -1525,7 +1410,20 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->media_player_command(msg);
 | 
			
		||||
  this->camera_image(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->climate_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
@@ -1541,6 +1439,19 @@ void APIServerConnection::on_number_command_request(const NumberCommandRequest &
 | 
			
		||||
  this->number_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->text_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
@@ -1554,8 +1465,8 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest &
 | 
			
		||||
  this->select_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) {
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
@@ -1564,11 +1475,11 @@ void APIServerConnection::on_siren_command_request(const SirenCommandRequest &ms
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->siren_command(msg);
 | 
			
		||||
  this->button_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
@@ -1577,11 +1488,11 @@ void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->switch_command(msg);
 | 
			
		||||
  this->lock_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) {
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
@@ -1590,7 +1501,33 @@ void APIServerConnection::on_text_command_request(const TextCommandRequest &msg)
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->text_command(msg);
 | 
			
		||||
  this->valve_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->media_player_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATE
 | 
			
		||||
void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->date_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
@@ -1606,6 +1543,19 @@ void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg)
 | 
			
		||||
  this->time_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->datetime_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
@@ -1619,19 +1569,6 @@ void APIServerConnection::on_update_command_request(const UpdateCommandRequest &
 | 
			
		||||
  this->update_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->valve_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
    const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
			
		||||
@@ -1768,19 +1705,6 @@ void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
  this->unsubscribe_bluetooth_le_advertisements(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->bluetooth_scanner_set_mode(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
// This file was automatically generated with a tool.
 | 
			
		||||
// See script/api_protobuf/api_protobuf.py
 | 
			
		||||
// See scripts/api_protobuf/api_protobuf.py
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "api_pb2.h"
 | 
			
		||||
@@ -136,15 +136,6 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
  virtual void on_select_command_request(const SelectCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
  bool send_list_entities_siren_response(const ListEntitiesSirenResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
  bool send_siren_state_response(const SirenStateResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
  virtual void on_siren_command_request(const SirenCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
  bool send_list_entities_lock_response(const ListEntitiesLockResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
@@ -243,12 +234,6 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_scanner_state_response(const BluetoothScannerStateResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
@@ -373,60 +358,57 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  virtual void button_command(const ButtonCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
  virtual void camera_image(const CameraImageRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
  virtual void climate_command(const ClimateCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
  virtual void cover_command(const CoverCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATE
 | 
			
		||||
  virtual void date_command(const DateCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_FAN
 | 
			
		||||
  virtual void fan_command(const FanCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LIGHT
 | 
			
		||||
  virtual void light_command(const LightCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
  virtual void lock_command(const LockCommandRequest &msg) = 0;
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  virtual void switch_command(const SwitchCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
  virtual void camera_image(const CameraImageRequest &msg) = 0;
 | 
			
		||||
#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
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
  virtual void text_command(const TextCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
  virtual void select_command(const SelectCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
  virtual void siren_command(const SirenCommandRequest &msg) = 0;
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  virtual void button_command(const ButtonCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  virtual void switch_command(const SwitchCommandRequest &msg) = 0;
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
  virtual void lock_command(const LockCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
  virtual void text_command(const TextCommandRequest &msg) = 0;
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
  virtual void valve_command(const ValveCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATE
 | 
			
		||||
  virtual void date_command(const DateCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
  virtual void time_command(const TimeCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  virtual void update_command(const UpdateCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
  virtual void valve_command(const ValveCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -458,9 +440,6 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -490,60 +469,57 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  void on_button_command_request(const ButtonCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
  void on_camera_image_request(const CameraImageRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
  void on_climate_command_request(const ClimateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
  void on_cover_command_request(const CoverCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATE
 | 
			
		||||
  void on_date_command_request(const DateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_FAN
 | 
			
		||||
  void on_fan_command_request(const FanCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LIGHT
 | 
			
		||||
  void on_light_command_request(const LightCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
  void on_lock_command_request(const LockCommandRequest &msg) override;
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  void on_switch_command_request(const SwitchCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
  void on_camera_image_request(const CameraImageRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#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
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
  void on_text_command_request(const TextCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
  void on_select_command_request(const SelectCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
  void on_siren_command_request(const SirenCommandRequest &msg) override;
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  void on_button_command_request(const ButtonCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  void on_switch_command_request(const SwitchCommandRequest &msg) override;
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
  void on_lock_command_request(const LockCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
  void on_text_command_request(const TextCommandRequest &msg) override;
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
  void on_valve_command_request(const ValveCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATE
 | 
			
		||||
  void on_date_command_request(const DateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
  void on_time_command_request(const TimeCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  void on_update_command_request(const UpdateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
  void on_valve_command_request(const ValveCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -575,9 +551,6 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
  void on_unsubscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
      const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -1,361 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "proto.h"
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
class ProtoSize {
 | 
			
		||||
 public:
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief ProtoSize class for Protocol Buffer serialization size calculation
 | 
			
		||||
   *
 | 
			
		||||
   * This class provides static methods to calculate the exact byte counts needed
 | 
			
		||||
   * for encoding various Protocol Buffer field types. All methods are designed to be
 | 
			
		||||
   * efficient for the common case where many fields have default values.
 | 
			
		||||
   *
 | 
			
		||||
   * Implements Protocol Buffer encoding size calculation according to:
 | 
			
		||||
   * https://protobuf.dev/programming-guides/encoding/
 | 
			
		||||
   *
 | 
			
		||||
   * Key features:
 | 
			
		||||
   * - Early-return optimization for zero/default values
 | 
			
		||||
   * - Direct total_size updates to avoid unnecessary additions
 | 
			
		||||
   * - Specialized handling for different field types according to protobuf spec
 | 
			
		||||
   * - Templated helpers for repeated fields and messages
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
 | 
			
		||||
   *
 | 
			
		||||
   * @param value The uint32_t value to calculate size for
 | 
			
		||||
   * @return The number of bytes needed to encode the value
 | 
			
		||||
   */
 | 
			
		||||
  static inline uint32_t varint(uint32_t value) {
 | 
			
		||||
    // Optimized varint size calculation using leading zeros
 | 
			
		||||
    // Each 7 bits requires one byte in the varint encoding
 | 
			
		||||
    if (value < 128)
 | 
			
		||||
      return 1;  // 7 bits, common case for small values
 | 
			
		||||
 | 
			
		||||
    // For larger values, count bytes needed based on the position of the highest bit set
 | 
			
		||||
    if (value < 16384) {
 | 
			
		||||
      return 2;  // 14 bits
 | 
			
		||||
    } else if (value < 2097152) {
 | 
			
		||||
      return 3;  // 21 bits
 | 
			
		||||
    } else if (value < 268435456) {
 | 
			
		||||
      return 4;  // 28 bits
 | 
			
		||||
    } else {
 | 
			
		||||
      return 5;  // 32 bits (maximum for uint32_t)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates the size in bytes needed to encode a uint64_t value as a varint
 | 
			
		||||
   *
 | 
			
		||||
   * @param value The uint64_t value to calculate size for
 | 
			
		||||
   * @return The number of bytes needed to encode the value
 | 
			
		||||
   */
 | 
			
		||||
  static inline uint32_t varint(uint64_t value) {
 | 
			
		||||
    // Handle common case of values fitting in uint32_t (vast majority of use cases)
 | 
			
		||||
    if (value <= UINT32_MAX) {
 | 
			
		||||
      return varint(static_cast<uint32_t>(value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // For larger values, determine size based on highest bit position
 | 
			
		||||
    if (value < (1ULL << 35)) {
 | 
			
		||||
      return 5;  // 35 bits
 | 
			
		||||
    } else if (value < (1ULL << 42)) {
 | 
			
		||||
      return 6;  // 42 bits
 | 
			
		||||
    } else if (value < (1ULL << 49)) {
 | 
			
		||||
      return 7;  // 49 bits
 | 
			
		||||
    } else if (value < (1ULL << 56)) {
 | 
			
		||||
      return 8;  // 56 bits
 | 
			
		||||
    } else if (value < (1ULL << 63)) {
 | 
			
		||||
      return 9;  // 63 bits
 | 
			
		||||
    } else {
 | 
			
		||||
      return 10;  // 64 bits (maximum for uint64_t)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates the size in bytes needed to encode an int32_t value as a varint
 | 
			
		||||
   *
 | 
			
		||||
   * Special handling is needed for negative values, which are sign-extended to 64 bits
 | 
			
		||||
   * in Protocol Buffers, resulting in a 10-byte varint.
 | 
			
		||||
   *
 | 
			
		||||
   * @param value The int32_t value to calculate size for
 | 
			
		||||
   * @return The number of bytes needed to encode the value
 | 
			
		||||
   */
 | 
			
		||||
  static inline uint32_t varint(int32_t value) {
 | 
			
		||||
    // Negative values are sign-extended to 64 bits in protocol buffers,
 | 
			
		||||
    // which always results in a 10-byte varint for negative int32
 | 
			
		||||
    if (value < 0) {
 | 
			
		||||
      return 10;  // Negative int32 is always 10 bytes long
 | 
			
		||||
    }
 | 
			
		||||
    // For non-negative values, use the uint32_t implementation
 | 
			
		||||
    return varint(static_cast<uint32_t>(value));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates the size in bytes needed to encode an int64_t value as a varint
 | 
			
		||||
   *
 | 
			
		||||
   * @param value The int64_t value to calculate size for
 | 
			
		||||
   * @return The number of bytes needed to encode the value
 | 
			
		||||
   */
 | 
			
		||||
  static inline uint32_t varint(int64_t value) {
 | 
			
		||||
    // For int64_t, we convert to uint64_t and calculate the size
 | 
			
		||||
    // This works because the bit pattern determines the encoding size,
 | 
			
		||||
    // and we've handled negative int32 values as a special case above
 | 
			
		||||
    return varint(static_cast<uint64_t>(value));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates the size in bytes needed to encode a field ID and wire type
 | 
			
		||||
   *
 | 
			
		||||
   * @param field_id The field identifier
 | 
			
		||||
   * @param type The wire type value (from the WireType enum in the protobuf spec)
 | 
			
		||||
   * @return The number of bytes needed to encode the field ID and wire type
 | 
			
		||||
   */
 | 
			
		||||
  static inline uint32_t field(uint32_t field_id, uint32_t type) {
 | 
			
		||||
    uint32_t tag = (field_id << 3) | (type & 0b111);
 | 
			
		||||
    return varint(tag);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Common parameters for all add_*_field methods
 | 
			
		||||
   *
 | 
			
		||||
   * All add_*_field methods follow these common patterns:
 | 
			
		||||
   *
 | 
			
		||||
   * @param total_size Reference to the total message size to update
 | 
			
		||||
   * @param field_id_size Pre-calculated size of the field ID in bytes
 | 
			
		||||
   * @param value The value to calculate size for (type varies)
 | 
			
		||||
   * @param force Whether to calculate size even if the value is default/zero/empty
 | 
			
		||||
   *
 | 
			
		||||
   * Each method follows this implementation pattern:
 | 
			
		||||
   * 1. Skip calculation if value is default (0, false, empty) and not forced
 | 
			
		||||
   * 2. Calculate the size based on the field's encoding rules
 | 
			
		||||
   * 3. Add the field_id_size + calculated value size to total_size
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of an int32 field to the total message size
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate and directly add to total_size
 | 
			
		||||
    if (value < 0) {
 | 
			
		||||
      // Negative values are encoded as 10-byte varints in protobuf
 | 
			
		||||
      total_size += field_id_size + 10;
 | 
			
		||||
    } else {
 | 
			
		||||
      // For non-negative values, use the standard varint size
 | 
			
		||||
      total_size += field_id_size + varint(static_cast<uint32_t>(value));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a uint32 field to the total message size
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value,
 | 
			
		||||
                                      bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate and directly add to total_size
 | 
			
		||||
    total_size += field_id_size + varint(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a boolean field to the total message size
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value, bool force = false) {
 | 
			
		||||
    // Skip calculation if value is false and not forced
 | 
			
		||||
    if (!value && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Boolean fields always use 1 byte when true
 | 
			
		||||
    total_size += field_id_size + 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a fixed field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * Fixed fields always take exactly N bytes (4 for fixed32/float, 8 for fixed64/double).
 | 
			
		||||
   *
 | 
			
		||||
   * @tparam NumBytes The number of bytes for this fixed field (4 or 8)
 | 
			
		||||
   * @param is_nonzero Whether the value is non-zero
 | 
			
		||||
   */
 | 
			
		||||
  template<uint32_t NumBytes>
 | 
			
		||||
  static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero,
 | 
			
		||||
                                     bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (!is_nonzero && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Fixed fields always take exactly NumBytes
 | 
			
		||||
    total_size += field_id_size + NumBytes;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of an enum field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * Enum fields are encoded as uint32 varints.
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value, bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Enums are encoded as uint32
 | 
			
		||||
    total_size += field_id_size + varint(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a sint32 field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * Sint32 fields use ZigZag encoding, which is more efficient for negative values.
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
 | 
			
		||||
    uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
 | 
			
		||||
    total_size += field_id_size + varint(zigzag);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of an int64 field to the total message size
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate and directly add to total_size
 | 
			
		||||
    total_size += field_id_size + varint(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a uint64 field to the total message size
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value,
 | 
			
		||||
                                      bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate and directly add to total_size
 | 
			
		||||
    total_size += field_id_size + varint(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a sint64 field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * Sint64 fields use ZigZag encoding, which is more efficient for negative values.
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_sint64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ZigZag encoding for sint64: (n << 1) ^ (n >> 63)
 | 
			
		||||
    uint64_t zigzag = (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
 | 
			
		||||
    total_size += field_id_size + varint(zigzag);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a string/bytes field to the total message size
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str,
 | 
			
		||||
                                      bool force = false) {
 | 
			
		||||
    // Skip calculation if string is empty and not forced
 | 
			
		||||
    if (str.empty() && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate and directly add to total_size
 | 
			
		||||
    const uint32_t str_size = static_cast<uint32_t>(str.size());
 | 
			
		||||
    total_size += field_id_size + varint(str_size) + str_size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a nested message field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * This helper function directly updates the total_size reference if the nested size
 | 
			
		||||
   * is greater than zero or force is true.
 | 
			
		||||
   *
 | 
			
		||||
   * @param nested_size The pre-calculated size of the nested message
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size,
 | 
			
		||||
                                       bool force = false) {
 | 
			
		||||
    // Skip calculation if nested message is empty and not forced
 | 
			
		||||
    if (nested_size == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate and directly add to total_size
 | 
			
		||||
    // Field ID + length varint + nested message content
 | 
			
		||||
    total_size += field_id_size + varint(nested_size) + nested_size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a nested message field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * This templated version directly takes a message object, calculates its size internally,
 | 
			
		||||
   * and updates the total_size reference. This eliminates the need for a temporary variable
 | 
			
		||||
   * at the call site.
 | 
			
		||||
   *
 | 
			
		||||
   * @tparam MessageType The type of the nested message (inferred from parameter)
 | 
			
		||||
   * @param message The nested message object
 | 
			
		||||
   */
 | 
			
		||||
  template<typename MessageType>
 | 
			
		||||
  static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const MessageType &message,
 | 
			
		||||
                                        bool force = false) {
 | 
			
		||||
    uint32_t nested_size = 0;
 | 
			
		||||
    message.calculate_size(nested_size);
 | 
			
		||||
 | 
			
		||||
    // Use the base implementation with the calculated nested_size
 | 
			
		||||
    add_message_field(total_size, field_id_size, nested_size, force);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the sizes of all messages in a repeated field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * This helper processes a vector of message objects, calculating the size for each message
 | 
			
		||||
   * and adding it to the total size.
 | 
			
		||||
   *
 | 
			
		||||
   * @tparam MessageType The type of the nested messages in the vector
 | 
			
		||||
   * @param messages Vector of message objects
 | 
			
		||||
   */
 | 
			
		||||
  template<typename MessageType>
 | 
			
		||||
  static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size,
 | 
			
		||||
                                          const std::vector<MessageType> &messages) {
 | 
			
		||||
    // Skip if the vector is empty
 | 
			
		||||
    if (messages.empty()) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // For repeated fields, always use force=true
 | 
			
		||||
    for (const auto &message : messages) {
 | 
			
		||||
      add_message_object(total_size, field_id_size, message, true);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -126,29 +126,19 @@ void APIServer::loop() {
 | 
			
		||||
    conn->start();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Process clients and remove disconnected ones in a single pass
 | 
			
		||||
  if (!this->clients_.empty()) {
 | 
			
		||||
    size_t client_index = 0;
 | 
			
		||||
    while (client_index < this->clients_.size()) {
 | 
			
		||||
      auto &client = this->clients_[client_index];
 | 
			
		||||
 | 
			
		||||
      if (client->remove_) {
 | 
			
		||||
        // Handle disconnection
 | 
			
		||||
        this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
 | 
			
		||||
        ESP_LOGV(TAG, "Removing connection to %s", client->client_info_.c_str());
 | 
			
		||||
 | 
			
		||||
        // Swap with the last element and pop (avoids expensive vector shifts)
 | 
			
		||||
        if (client_index < this->clients_.size() - 1) {
 | 
			
		||||
          std::swap(this->clients_[client_index], this->clients_.back());
 | 
			
		||||
  // Partition clients into remove and active
 | 
			
		||||
  auto new_end = std::partition(this->clients_.begin(), this->clients_.end(),
 | 
			
		||||
                                [](const std::unique_ptr<APIConnection> &conn) { return !conn->remove_; });
 | 
			
		||||
  // print disconnection messages
 | 
			
		||||
  for (auto it = new_end; it != this->clients_.end(); ++it) {
 | 
			
		||||
    this->client_disconnected_trigger_->trigger((*it)->client_info_, (*it)->client_peername_);
 | 
			
		||||
    ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str());
 | 
			
		||||
  }
 | 
			
		||||
        this->clients_.pop_back();
 | 
			
		||||
        // Don't increment client_index since we need to process the swapped element
 | 
			
		||||
      } else {
 | 
			
		||||
        // Process active client
 | 
			
		||||
  // resize vector
 | 
			
		||||
  this->clients_.erase(new_end, this->clients_.end());
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->loop();
 | 
			
		||||
        client_index++;  // Move to next client
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->reboot_timeout_ != 0) {
 | 
			
		||||
 
 | 
			
		||||
@@ -29,8 +29,9 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None:
 | 
			
		||||
    port: int = int(conf[CONF_PORT])
 | 
			
		||||
    password: str = conf[CONF_PASSWORD]
 | 
			
		||||
    noise_psk: str | None = None
 | 
			
		||||
    if CONF_ENCRYPTION in conf:
 | 
			
		||||
        noise_psk = conf[CONF_ENCRYPTION][CONF_KEY]
 | 
			
		||||
    if encryption_config := conf.get(CONF_ENCRYPTION):
 | 
			
		||||
        if key := encryption_config.get(CONF_KEY):
 | 
			
		||||
            noise_psk = key
 | 
			
		||||
    _LOGGER.info("Starting log output from %s using esphome API", address)
 | 
			
		||||
    cli = APIClient(
 | 
			
		||||
        address,
 | 
			
		||||
 
 | 
			
		||||
@@ -20,26 +20,16 @@ class ProtoVarInt {
 | 
			
		||||
  explicit ProtoVarInt(uint64_t value) : value_(value) {}
 | 
			
		||||
 | 
			
		||||
  static optional<ProtoVarInt> parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed) {
 | 
			
		||||
    if (len == 0) {
 | 
			
		||||
    if (consumed != nullptr)
 | 
			
		||||
      *consumed = 0;
 | 
			
		||||
 | 
			
		||||
    if (len == 0)
 | 
			
		||||
      return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Most common case: single-byte varint (values 0-127)
 | 
			
		||||
    if ((buffer[0] & 0x80) == 0) {
 | 
			
		||||
      if (consumed != nullptr)
 | 
			
		||||
        *consumed = 1;
 | 
			
		||||
      return ProtoVarInt(buffer[0]);
 | 
			
		||||
    }
 | 
			
		||||
    uint64_t result = 0;
 | 
			
		||||
    uint8_t bitpos = 0;
 | 
			
		||||
 | 
			
		||||
    // General case for multi-byte varints
 | 
			
		||||
    // Since we know buffer[0]'s high bit is set, initialize with its value
 | 
			
		||||
    uint64_t result = buffer[0] & 0x7F;
 | 
			
		||||
    uint8_t bitpos = 7;
 | 
			
		||||
 | 
			
		||||
    // Start from the second byte since we've already processed the first
 | 
			
		||||
    for (uint32_t i = 1; i < len; i++) {
 | 
			
		||||
    for (uint32_t i = 0; i < len; i++) {
 | 
			
		||||
      uint8_t val = buffer[i];
 | 
			
		||||
      result |= uint64_t(val & 0x7F) << uint64_t(bitpos);
 | 
			
		||||
      bitpos += 7;
 | 
			
		||||
@@ -50,9 +40,7 @@ class ProtoVarInt {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (consumed != nullptr)
 | 
			
		||||
      *consumed = 0;
 | 
			
		||||
    return {};  // Incomplete or invalid varint
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint32_t as_uint32() const { return this->value_; }
 | 
			
		||||
@@ -83,34 +71,6 @@ class ProtoVarInt {
 | 
			
		||||
      return static_cast<int64_t>(this->value_ >> 1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * Encode the varint value to a pre-allocated buffer without bounds checking.
 | 
			
		||||
   *
 | 
			
		||||
   * @param buffer The pre-allocated buffer to write the encoded varint to
 | 
			
		||||
   * @param len The size of the buffer in bytes
 | 
			
		||||
   *
 | 
			
		||||
   * @note The caller is responsible for ensuring the buffer is large enough
 | 
			
		||||
   *       to hold the encoded value. Use ProtoSize::varint() to calculate
 | 
			
		||||
   *       the exact size needed before calling this method.
 | 
			
		||||
   * @note No bounds checking is performed for performance reasons.
 | 
			
		||||
   */
 | 
			
		||||
  void encode_to_buffer_unchecked(uint8_t *buffer, size_t len) {
 | 
			
		||||
    uint64_t val = this->value_;
 | 
			
		||||
    if (val <= 0x7F) {
 | 
			
		||||
      buffer[0] = val;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    size_t i = 0;
 | 
			
		||||
    while (val && i < len) {
 | 
			
		||||
      uint8_t temp = val & 0x7F;
 | 
			
		||||
      val >>= 7;
 | 
			
		||||
      if (val) {
 | 
			
		||||
        buffer[i++] = temp | 0x80;
 | 
			
		||||
      } else {
 | 
			
		||||
        buffer[i++] = temp;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  void encode(std::vector<uint8_t> &out) {
 | 
			
		||||
    uint64_t val = this->value_;
 | 
			
		||||
    if (val <= 0x7F) {
 | 
			
		||||
@@ -189,18 +149,6 @@ class ProtoWriteBuffer {
 | 
			
		||||
  void write(uint8_t value) { this->buffer_->push_back(value); }
 | 
			
		||||
  void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); }
 | 
			
		||||
  void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); }
 | 
			
		||||
  /**
 | 
			
		||||
   * Encode a field key (tag/wire type combination).
 | 
			
		||||
   *
 | 
			
		||||
   * @param field_id Field number (tag) in the protobuf message
 | 
			
		||||
   * @param type Wire type value:
 | 
			
		||||
   *   - 0: Varint (int32, int64, uint32, uint64, sint32, sint64, bool, enum)
 | 
			
		||||
   *   - 1: 64-bit (fixed64, sfixed64, double)
 | 
			
		||||
   *   - 2: Length-delimited (string, bytes, embedded messages, packed repeated fields)
 | 
			
		||||
   *   - 5: 32-bit (fixed32, sfixed32, float)
 | 
			
		||||
   *
 | 
			
		||||
   * Following https://protobuf.dev/programming-guides/encoding/#structure
 | 
			
		||||
   */
 | 
			
		||||
  void encode_field_raw(uint32_t field_id, uint32_t type) {
 | 
			
		||||
    uint32_t val = (field_id << 3) | (type & 0b111);
 | 
			
		||||
    this->encode_varint_raw(val);
 | 
			
		||||
@@ -209,7 +157,7 @@ class ProtoWriteBuffer {
 | 
			
		||||
    if (len == 0 && !force)
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    this->encode_field_raw(field_id, 2);  // type 2: Length-delimited string
 | 
			
		||||
    this->encode_field_raw(field_id, 2);
 | 
			
		||||
    this->encode_varint_raw(len);
 | 
			
		||||
    auto *data = reinterpret_cast<const uint8_t *>(string);
 | 
			
		||||
    this->buffer_->insert(this->buffer_->end(), data, data + len);
 | 
			
		||||
@@ -223,26 +171,26 @@ class ProtoWriteBuffer {
 | 
			
		||||
  void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
 | 
			
		||||
    if (value == 0 && !force)
 | 
			
		||||
      return;
 | 
			
		||||
    this->encode_field_raw(field_id, 0);  // type 0: Varint - uint32
 | 
			
		||||
    this->encode_field_raw(field_id, 0);
 | 
			
		||||
    this->encode_varint_raw(value);
 | 
			
		||||
  }
 | 
			
		||||
  void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) {
 | 
			
		||||
    if (value == 0 && !force)
 | 
			
		||||
      return;
 | 
			
		||||
    this->encode_field_raw(field_id, 0);  // type 0: Varint - uint64
 | 
			
		||||
    this->encode_field_raw(field_id, 0);
 | 
			
		||||
    this->encode_varint_raw(ProtoVarInt(value));
 | 
			
		||||
  }
 | 
			
		||||
  void encode_bool(uint32_t field_id, bool value, bool force = false) {
 | 
			
		||||
    if (!value && !force)
 | 
			
		||||
      return;
 | 
			
		||||
    this->encode_field_raw(field_id, 0);  // type 0: Varint - bool
 | 
			
		||||
    this->encode_field_raw(field_id, 0);
 | 
			
		||||
    this->write(0x01);
 | 
			
		||||
  }
 | 
			
		||||
  void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
 | 
			
		||||
    if (value == 0 && !force)
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    this->encode_field_raw(field_id, 5);  // type 5: 32-bit fixed32
 | 
			
		||||
    this->encode_field_raw(field_id, 5);
 | 
			
		||||
    this->write((value >> 0) & 0xFF);
 | 
			
		||||
    this->write((value >> 8) & 0xFF);
 | 
			
		||||
    this->write((value >> 16) & 0xFF);
 | 
			
		||||
@@ -252,7 +200,7 @@ class ProtoWriteBuffer {
 | 
			
		||||
    if (value == 0 && !force)
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    this->encode_field_raw(field_id, 1);  // type 1: 64-bit fixed64
 | 
			
		||||
    this->encode_field_raw(field_id, 5);
 | 
			
		||||
    this->write((value >> 0) & 0xFF);
 | 
			
		||||
    this->write((value >> 8) & 0xFF);
 | 
			
		||||
    this->write((value >> 16) & 0xFF);
 | 
			
		||||
@@ -306,7 +254,7 @@ class ProtoWriteBuffer {
 | 
			
		||||
    this->encode_uint64(field_id, uvalue, force);
 | 
			
		||||
  }
 | 
			
		||||
  template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
 | 
			
		||||
    this->encode_field_raw(field_id, 2);  // type 2: Length-delimited message
 | 
			
		||||
    this->encode_field_raw(field_id, 2);
 | 
			
		||||
    size_t begin = this->buffer_->size();
 | 
			
		||||
 | 
			
		||||
    value.encode(*this);
 | 
			
		||||
@@ -328,7 +276,6 @@ class ProtoMessage {
 | 
			
		||||
  virtual ~ProtoMessage() = default;
 | 
			
		||||
  virtual void encode(ProtoWriteBuffer buffer) const = 0;
 | 
			
		||||
  void decode(const uint8_t *buffer, size_t length);
 | 
			
		||||
  virtual void calculate_size(uint32_t &total_size) const = 0;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  std::string dump() const;
 | 
			
		||||
  virtual void dump_to(std::string &out) const = 0;
 | 
			
		||||
@@ -351,29 +298,13 @@ class ProtoService {
 | 
			
		||||
  virtual void on_fatal_error() = 0;
 | 
			
		||||
  virtual void on_unauthenticated_access() = 0;
 | 
			
		||||
  virtual void on_no_setup_connection() = 0;
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a buffer with a reserved size.
 | 
			
		||||
   * @param reserve_size The number of bytes to pre-allocate in the buffer. This is a hint
 | 
			
		||||
   *                     to optimize memory usage and avoid reallocations during encoding.
 | 
			
		||||
   *                     Implementations should aim to allocate at least this size.
 | 
			
		||||
   * @return A ProtoWriteBuffer object with the reserved size.
 | 
			
		||||
   */
 | 
			
		||||
  virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
 | 
			
		||||
  virtual ProtoWriteBuffer create_buffer() = 0;
 | 
			
		||||
  virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0;
 | 
			
		||||
  virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
 | 
			
		||||
 | 
			
		||||
  // Optimized method that pre-allocates buffer based on message size
 | 
			
		||||
  template<class C> bool send_message_(const C &msg, uint32_t message_type) {
 | 
			
		||||
    uint32_t msg_size = 0;
 | 
			
		||||
    msg.calculate_size(msg_size);
 | 
			
		||||
 | 
			
		||||
    // Create a pre-sized buffer
 | 
			
		||||
    auto buffer = this->create_buffer(msg_size);
 | 
			
		||||
 | 
			
		||||
    // Encode message into the buffer
 | 
			
		||||
    auto buffer = this->create_buffer();
 | 
			
		||||
    msg.encode(buffer);
 | 
			
		||||
 | 
			
		||||
    // Send the buffer
 | 
			
		||||
    return this->send_buffer(buffer, message_type);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,10 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/as3935/as3935.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace as3935_i2c {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace as7341 {
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_CHIP_ID = 0x09;
 | 
			
		||||
static const uint8_t AS7341_CHIP_ID = 0X09;
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_CONFIG = 0x70;
 | 
			
		||||
static const uint8_t AS7341_LED = 0x74;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,8 +14,11 @@ namespace esphome {
 | 
			
		||||
namespace at581x {
 | 
			
		||||
 | 
			
		||||
class AT581XComponent : public Component, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
 protected:
 | 
			
		||||
  switch_::Switch *rf_power_switch_{nullptr};
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  void set_rf_power_switch(switch_::Switch *s) {
 | 
			
		||||
    this->rf_power_switch_ = s;
 | 
			
		||||
    s->turn_on();
 | 
			
		||||
@@ -45,9 +48,6 @@ class AT581XComponent : public Component, public i2c::I2CDevice {
 | 
			
		||||
  bool i2c_read_reg(uint8_t addr, uint8_t &data);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  switch_::Switch *rf_power_switch_{nullptr};
 | 
			
		||||
#endif
 | 
			
		||||
  int freq_;
 | 
			
		||||
  int self_check_time_ms_;   /*!< Power-on self-test time, range: 0 ~ 65536 ms */
 | 
			
		||||
  int protect_time_ms_;      /*!< Protection time, recommended 1000 ms */
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,5 @@ import esphome.codegen as cg
 | 
			
		||||
CODEOWNERS = ["@circuitsetup", "@descipher"]
 | 
			
		||||
 | 
			
		||||
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
 | 
			
		||||
ATM90E32Component = atm90e32_ns.class_("ATM90E32Component", cg.Component)
 | 
			
		||||
 | 
			
		||||
CONF_ATM90E32_ID = "atm90e32_id"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#include "atm90e32.h"
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include <cmath>
 | 
			
		||||
#include "atm90e32_reg.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atm90e32 {
 | 
			
		||||
@@ -11,86 +11,117 @@ void ATM90E32Component::loop() {
 | 
			
		||||
  if (this->get_publish_interval_flag_()) {
 | 
			
		||||
    this->set_publish_interval_flag_(false);
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].voltage_sensor_ != nullptr)
 | 
			
		||||
      if (this->phase_[phase].voltage_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].voltage_ = this->get_phase_voltage_(phase);
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].current_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].current_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].current_ = this->get_phase_current_(phase);
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].power_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].power_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].active_power_ = this->get_phase_active_power_(phase);
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].power_factor_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].power_factor_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].power_factor_ = this->get_phase_power_factor_(phase);
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].reactive_power_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].reactive_power_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].reactive_power_ = this->get_phase_reactive_power_(phase);
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].apparent_power_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].apparent_power_ = this->get_phase_apparent_power_(phase);
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].forward_active_energy_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].forward_active_energy_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].forward_active_energy_ = this->get_phase_forward_active_energy_(phase);
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].reverse_active_energy_ = this->get_phase_reverse_active_energy_(phase);
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].phase_angle_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].phase_angle_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].phase_angle_ = this->get_phase_angle_(phase);
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].harmonic_active_power_ = this->get_phase_harmonic_active_power_(phase);
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].peak_current_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].peak_current_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].peak_current_ = this->get_phase_peak_current_(phase);
 | 
			
		||||
 | 
			
		||||
      // After the local store is collected we can publish them trusting they are within +-1 hardware sampling
 | 
			
		||||
      if (this->phase_[phase].voltage_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // After the local store in collected we can publish them trusting they are withing +-1 haardware sampling
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].voltage_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].voltage_sensor_->publish_state(this->get_local_phase_voltage_(phase));
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].current_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].current_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].current_sensor_->publish_state(this->get_local_phase_current_(phase));
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].power_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].power_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].power_sensor_->publish_state(this->get_local_phase_active_power_(phase));
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].power_factor_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].power_factor_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].power_factor_sensor_->publish_state(this->get_local_phase_power_factor_(phase));
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].reactive_power_sensor_ != nullptr)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].reactive_power_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].reactive_power_sensor_->publish_state(this->get_local_phase_reactive_power_(phase));
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].apparent_power_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].apparent_power_sensor_->publish_state(this->get_local_phase_apparent_power_(phase));
 | 
			
		||||
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].forward_active_energy_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].forward_active_energy_sensor_->publish_state(
 | 
			
		||||
            this->get_local_phase_forward_active_energy_(phase));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].reverse_active_energy_sensor_->publish_state(
 | 
			
		||||
            this->get_local_phase_reverse_active_energy_(phase));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].phase_angle_sensor_ != nullptr)
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].phase_angle_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].phase_angle_sensor_->publish_state(this->get_local_phase_angle_(phase));
 | 
			
		||||
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].harmonic_active_power_sensor_->publish_state(
 | 
			
		||||
            this->get_local_phase_harmonic_active_power_(phase));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].peak_current_sensor_ != nullptr)
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].peak_current_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].peak_current_sensor_->publish_state(this->get_local_phase_peak_current_(phase));
 | 
			
		||||
      }
 | 
			
		||||
    if (this->freq_sensor_ != nullptr)
 | 
			
		||||
    }
 | 
			
		||||
    if (this->freq_sensor_ != nullptr) {
 | 
			
		||||
      this->freq_sensor_->publish_state(this->get_frequency_());
 | 
			
		||||
 | 
			
		||||
    if (this->chip_temperature_sensor_ != nullptr)
 | 
			
		||||
    }
 | 
			
		||||
    if (this->chip_temperature_sensor_ != nullptr) {
 | 
			
		||||
      this->chip_temperature_sensor_->publish_state(this->get_chip_temperature_());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::update() {
 | 
			
		||||
  if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) {
 | 
			
		||||
@@ -99,30 +130,82 @@ void ATM90E32Component::update() {
 | 
			
		||||
  }
 | 
			
		||||
  this->set_publish_interval_flag_(true);
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  this->check_phase_status();
 | 
			
		||||
  this->check_over_current();
 | 
			
		||||
  this->check_freq_status();
 | 
			
		||||
#endif
 | 
			
		||||
void ATM90E32Component::restore_calibrations_() {
 | 
			
		||||
  if (enable_offset_calibration_) {
 | 
			
		||||
    this->pref_.load(&this->offset_phase_);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::run_offset_calibrations() {
 | 
			
		||||
  // Run the calibrations and
 | 
			
		||||
  // Setup voltage and current calibration offsets for PHASE A
 | 
			
		||||
  this->offset_phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA);
 | 
			
		||||
  this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_);  // C Voltage offset
 | 
			
		||||
  this->offset_phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA);
 | 
			
		||||
  this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_);  // C Current offset
 | 
			
		||||
  // Setup voltage and current calibration offsets for PHASE B
 | 
			
		||||
  this->offset_phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB);
 | 
			
		||||
  this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_);  // C Voltage offset
 | 
			
		||||
  this->offset_phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB);
 | 
			
		||||
  this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_);  // C Current offset
 | 
			
		||||
  // Setup voltage and current calibration offsets for PHASE C
 | 
			
		||||
  this->offset_phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC);
 | 
			
		||||
  this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_);  // C Voltage offset
 | 
			
		||||
  this->offset_phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC);
 | 
			
		||||
  this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_);  // C Current offset
 | 
			
		||||
  this->pref_.save(&this->offset_phase_);
 | 
			
		||||
  ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_,
 | 
			
		||||
           this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_);
 | 
			
		||||
  ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_,
 | 
			
		||||
           this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::clear_offset_calibrations() {
 | 
			
		||||
  // Clear the calibrations and
 | 
			
		||||
  this->offset_phase_[PHASEA].voltage_offset_ = 0;
 | 
			
		||||
  this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_);  // C Voltage offset
 | 
			
		||||
  this->offset_phase_[PHASEA].current_offset_ = 0;
 | 
			
		||||
  this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_);  // C Current offset
 | 
			
		||||
  this->offset_phase_[PHASEB].voltage_offset_ = 0;
 | 
			
		||||
  this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_);  // C Voltage offset
 | 
			
		||||
  this->offset_phase_[PHASEB].current_offset_ = 0;
 | 
			
		||||
  this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_);  // C Current offset
 | 
			
		||||
  this->offset_phase_[PHASEC].voltage_offset_ = 0;
 | 
			
		||||
  this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_);  // C Voltage offset
 | 
			
		||||
  this->offset_phase_[PHASEC].current_offset_ = 0;
 | 
			
		||||
  this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_);  // C Current offset
 | 
			
		||||
  this->pref_.save(&this->offset_phase_);
 | 
			
		||||
  ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_,
 | 
			
		||||
           this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_);
 | 
			
		||||
  ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_,
 | 
			
		||||
           this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component...");
 | 
			
		||||
  this->spi_setup();
 | 
			
		||||
 | 
			
		||||
  if (this->enable_offset_calibration_) {
 | 
			
		||||
    uint32_t hash = fnv1_hash(App.get_friendly_name());
 | 
			
		||||
    this->pref_ = global_preferences->make_preference<Calibration[3]>(hash, true);
 | 
			
		||||
    this->restore_calibrations_();
 | 
			
		||||
  }
 | 
			
		||||
  uint16_t mmode0 = 0x87;  // 3P4W 50Hz
 | 
			
		||||
  uint16_t high_thresh = 0;
 | 
			
		||||
  uint16_t low_thresh = 0;
 | 
			
		||||
 | 
			
		||||
  if (line_freq_ == 60) {
 | 
			
		||||
    mmode0 |= 1 << 12;  // sets 12th bit to 1, 60Hz
 | 
			
		||||
    // for freq threshold registers
 | 
			
		||||
    high_thresh = 6300;  // 63.00 Hz
 | 
			
		||||
    low_thresh = 5700;   // 57.00 Hz
 | 
			
		||||
  } else {
 | 
			
		||||
    high_thresh = 5300;  // 53.00 Hz
 | 
			
		||||
    low_thresh = 4700;   // 47.00 Hz
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (current_phases_ == 2) {
 | 
			
		||||
@@ -133,83 +216,33 @@ void ATM90E32Component::setup() {
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A);    // Perform soft reset
 | 
			
		||||
  delay(6);                                               // Wait for the minimum 5ms + 1ms
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);  // enable register config access
 | 
			
		||||
  if (!this->validate_spi_read_(0x55AA, "setup()")) {
 | 
			
		||||
  if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != 0x55AA) {
 | 
			
		||||
    ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_METEREN, 0x0001);        // Enable Metering
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_SAGPEAKDETCFG, 0xFF3F);  // Peak Detector time (15:8) 255ms, Sag Period (7:0) 63ms
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_SAGPEAKDETCFG, 0xFF3F);  // Peak Detector time ms (15:8), Sag Period ms (7:0)
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_PLCONSTH, 0x0861);       // PL Constant MSB (default) = 140625000
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_PLCONSTL, 0xC468);       // PL Constant LSB (default)
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654);       // Zero crossing (ZX2, ZX1, ZX0) pin config
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654);       // ZX2, ZX1, ZX0 pin config
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_MMODE0, mmode0);         // Mode Config (frequency set in main program)
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_);      // PGA Gain Configuration for Current Channels
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_FREQHITH, high_thresh);  // Frequency high threshold
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_FREQLOTH, low_thresh);   // Frequency low threshold
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x1D4C);       // All Active Startup Power Threshold - 0.02A/0.00032 = 7500
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x1D4C);       // All Reactive Startup Power Threshold - 50%
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C);       // All Reactive Startup Power Threshold - 50%
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE);       // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE);       // Each phase Reactive Phase Threshold - 10%
 | 
			
		||||
 | 
			
		||||
  if (this->enable_offset_calibration_) {
 | 
			
		||||
    // Initialize flash storage for offset calibrations
 | 
			
		||||
    uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_->dump_summary());
 | 
			
		||||
    this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
 | 
			
		||||
    this->restore_offset_calibrations_();
 | 
			
		||||
 | 
			
		||||
    // Initialize flash storage for power offset calibrations
 | 
			
		||||
    uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_->dump_summary());
 | 
			
		||||
    this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
 | 
			
		||||
    this->restore_power_offset_calibrations_();
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Power & Voltage/Current offset calibration is disabled. Using config file values.");
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; ++phase) {
 | 
			
		||||
      this->write16_(this->voltage_offset_registers[phase],
 | 
			
		||||
                     static_cast<uint16_t>(this->offset_phase_[phase].voltage_offset_));
 | 
			
		||||
      this->write16_(this->current_offset_registers[phase],
 | 
			
		||||
                     static_cast<uint16_t>(this->offset_phase_[phase].current_offset_));
 | 
			
		||||
      this->write16_(this->power_offset_registers[phase],
 | 
			
		||||
                     static_cast<uint16_t>(this->power_offset_phase_[phase].active_power_offset));
 | 
			
		||||
      this->write16_(this->reactive_power_offset_registers[phase],
 | 
			
		||||
                     static_cast<uint16_t>(this->power_offset_phase_[phase].reactive_power_offset));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->enable_gain_calibration_) {
 | 
			
		||||
    // Initialize flash storage for gain calibration
 | 
			
		||||
    uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_->dump_summary());
 | 
			
		||||
    this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
 | 
			
		||||
    this->restore_gain_calibrations_();
 | 
			
		||||
 | 
			
		||||
    if (this->using_saved_calibrations_) {
 | 
			
		||||
      ESP_LOGI(TAG, "[CALIBRATION] Successfully restored gain calibration from memory.");
 | 
			
		||||
    } else {
 | 
			
		||||
      for (uint8_t phase = 0; phase < 3; ++phase) {
 | 
			
		||||
        this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
 | 
			
		||||
        this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Gain calibration is disabled. Using config file values.");
 | 
			
		||||
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; ++phase) {
 | 
			
		||||
      this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
 | 
			
		||||
      this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Sag threshold (78%)
 | 
			
		||||
  uint16_t sagth = calculate_voltage_threshold(line_freq_, this->phase_[0].voltage_gain_, 0.78f);
 | 
			
		||||
  // Overvoltage threshold (122%)
 | 
			
		||||
  uint16_t ovth = calculate_voltage_threshold(line_freq_, this->phase_[0].voltage_gain_, 1.22f);
 | 
			
		||||
 | 
			
		||||
  // Write to registers
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_SAGTH, sagth);
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_OVTH, ovth);
 | 
			
		||||
 | 
			
		||||
  // Setup voltage and current gain for PHASE A
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[PHASEA].voltage_gain_);  // A Voltage rms gain
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[PHASEA].ct_gain_);       // A line current gain
 | 
			
		||||
  // Setup voltage and current gain for PHASE B
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[PHASEB].voltage_gain_);  // B Voltage rms gain
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[PHASEB].ct_gain_);       // B line current gain
 | 
			
		||||
  // Setup voltage and current gain for PHASE C
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[PHASEC].voltage_gain_);  // C Voltage rms gain
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[PHASEC].ct_gain_);       // C line current gain
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);                         // end configuration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -224,7 +257,6 @@ void ATM90E32Component::dump_config() {
 | 
			
		||||
  LOG_SENSOR("  ", "Current A", this->phase_[PHASEA].current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Power A", this->phase_[PHASEA].power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Reactive Power A", this->phase_[PHASEA].reactive_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Apparent Power A", this->phase_[PHASEA].apparent_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "PF A", this->phase_[PHASEA].power_factor_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Forward Energy A", this->phase_[PHASEA].forward_active_energy_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Reverse Energy A", this->phase_[PHASEA].reverse_active_energy_sensor_);
 | 
			
		||||
@@ -235,24 +267,22 @@ void ATM90E32Component::dump_config() {
 | 
			
		||||
  LOG_SENSOR("  ", "Current B", this->phase_[PHASEB].current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Power B", this->phase_[PHASEB].power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Reactive Power B", this->phase_[PHASEB].reactive_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Apparent Power B", this->phase_[PHASEB].apparent_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "PF B", this->phase_[PHASEB].power_factor_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Forward Energy B", this->phase_[PHASEB].forward_active_energy_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Reverse Energy B", this->phase_[PHASEB].reverse_active_energy_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Harmonic Power B", this->phase_[PHASEB].harmonic_active_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Phase Angle B", this->phase_[PHASEB].phase_angle_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Peak Current B", this->phase_[PHASEB].peak_current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Harmonic Power A", this->phase_[PHASEB].harmonic_active_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Phase Angle A", this->phase_[PHASEB].phase_angle_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Peak Current A", this->phase_[PHASEB].peak_current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Voltage C", this->phase_[PHASEC].voltage_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Current C", this->phase_[PHASEC].current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Power C", this->phase_[PHASEC].power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Reactive Power C", this->phase_[PHASEC].reactive_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Apparent Power C", this->phase_[PHASEC].apparent_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "PF C", this->phase_[PHASEC].power_factor_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Forward Energy C", this->phase_[PHASEC].forward_active_energy_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Reverse Energy C", this->phase_[PHASEC].reverse_active_energy_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Harmonic Power C", this->phase_[PHASEC].harmonic_active_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Phase Angle C", this->phase_[PHASEC].phase_angle_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Peak Current C", this->phase_[PHASEC].peak_current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Harmonic Power A", this->phase_[PHASEC].harmonic_active_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Phase Angle A", this->phase_[PHASEC].phase_angle_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Peak Current A", this->phase_[PHASEC].peak_current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Frequency", this->freq_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Chip Temp", this->chip_temperature_sensor_);
 | 
			
		||||
}
 | 
			
		||||
@@ -268,7 +298,7 @@ uint16_t ATM90E32Component::read16_(uint16_t a_register) {
 | 
			
		||||
  uint8_t data[2];
 | 
			
		||||
  uint16_t output;
 | 
			
		||||
  this->enable();
 | 
			
		||||
  delay_microseconds_safe(1);  // min delay between CS low and first SCK is 200ns - 1ms is plenty
 | 
			
		||||
  delay_microseconds_safe(10);
 | 
			
		||||
  this->write_byte(addrh);
 | 
			
		||||
  this->write_byte(addrl);
 | 
			
		||||
  this->read_array(data, 2);
 | 
			
		||||
@@ -298,7 +328,8 @@ void ATM90E32Component::write16_(uint16_t a_register, uint16_t val) {
 | 
			
		||||
  this->write_byte16(a_register);
 | 
			
		||||
  this->write_byte16(val);
 | 
			
		||||
  this->disable();
 | 
			
		||||
  this->validate_spi_read_(val, "write16()");
 | 
			
		||||
  if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != val)
 | 
			
		||||
    ESP_LOGW(TAG, "SPI write error 0x%04X val 0x%04X", a_register, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; }
 | 
			
		||||
@@ -309,8 +340,6 @@ float ATM90E32Component::get_local_phase_active_power_(uint8_t phase) { return t
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_local_phase_reactive_power_(uint8_t phase) { return this->phase_[phase].reactive_power_; }
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_local_phase_apparent_power_(uint8_t phase) { return this->phase_[phase].apparent_power_; }
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_local_phase_power_factor_(uint8_t phase) { return this->phase_[phase].power_factor_; }
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_local_phase_forward_active_energy_(uint8_t phase) {
 | 
			
		||||
@@ -331,7 +360,8 @@ float ATM90E32Component::get_local_phase_peak_current_(uint8_t phase) { return t
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_voltage_(uint8_t phase) {
 | 
			
		||||
  const uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMS + phase);
 | 
			
		||||
  this->validate_spi_read_(voltage, "get_phase_voltage()");
 | 
			
		||||
  if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != voltage)
 | 
			
		||||
    ESP_LOGW(TAG, "SPI URMS voltage register read error.");
 | 
			
		||||
  return (float) voltage / 100;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -341,7 +371,8 @@ float ATM90E32Component::get_phase_voltage_avg_(uint8_t phase) {
 | 
			
		||||
  uint16_t voltage = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < reads; i++) {
 | 
			
		||||
    voltage = this->read16_(ATM90E32_REGISTER_URMS + phase);
 | 
			
		||||
    this->validate_spi_read_(voltage, "get_phase_voltage_avg_()");
 | 
			
		||||
    if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != voltage)
 | 
			
		||||
      ESP_LOGW(TAG, "SPI URMS voltage register read error.");
 | 
			
		||||
    accumulation += voltage;
 | 
			
		||||
  }
 | 
			
		||||
  voltage = accumulation / reads;
 | 
			
		||||
@@ -355,7 +386,8 @@ float ATM90E32Component::get_phase_current_avg_(uint8_t phase) {
 | 
			
		||||
  uint16_t current = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < reads; i++) {
 | 
			
		||||
    current = this->read16_(ATM90E32_REGISTER_IRMS + phase);
 | 
			
		||||
    this->validate_spi_read_(current, "get_phase_current_avg_()");
 | 
			
		||||
    if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != current)
 | 
			
		||||
      ESP_LOGW(TAG, "SPI IRMS current register read error.");
 | 
			
		||||
    accumulation += current;
 | 
			
		||||
  }
 | 
			
		||||
  current = accumulation / reads;
 | 
			
		||||
@@ -365,7 +397,8 @@ float ATM90E32Component::get_phase_current_avg_(uint8_t phase) {
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_current_(uint8_t phase) {
 | 
			
		||||
  const uint16_t current = this->read16_(ATM90E32_REGISTER_IRMS + phase);
 | 
			
		||||
  this->validate_spi_read_(current, "get_phase_current_()");
 | 
			
		||||
  if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != current)
 | 
			
		||||
    ESP_LOGW(TAG, "SPI IRMS current register read error.");
 | 
			
		||||
  return (float) current / 1000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -379,15 +412,11 @@ float ATM90E32Component::get_phase_reactive_power_(uint8_t phase) {
 | 
			
		||||
  return val * 0.00032f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_apparent_power_(uint8_t phase) {
 | 
			
		||||
  const int val = this->read32_(ATM90E32_REGISTER_SMEAN + phase, ATM90E32_REGISTER_SMEANLSB + phase);
 | 
			
		||||
  return val * 0.00032f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_power_factor_(uint8_t phase) {
 | 
			
		||||
  uint16_t powerfactor = this->read16_(ATM90E32_REGISTER_PFMEAN + phase);  // unsigned to compare to lastspidata
 | 
			
		||||
  this->validate_spi_read_(powerfactor, "get_phase_power_factor_()");
 | 
			
		||||
  return (float) ((int16_t) powerfactor) / 1000;  // make it signed again
 | 
			
		||||
  const int16_t powerfactor = this->read16_(ATM90E32_REGISTER_PFMEAN + phase);
 | 
			
		||||
  if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != powerfactor)
 | 
			
		||||
    ESP_LOGW(TAG, "SPI power factor read error.");
 | 
			
		||||
  return (float) powerfactor / 1000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_forward_active_energy_(uint8_t phase) {
 | 
			
		||||
@@ -397,19 +426,17 @@ float ATM90E32Component::get_phase_forward_active_energy_(uint8_t phase) {
 | 
			
		||||
  } else {
 | 
			
		||||
    this->phase_[phase].cumulative_forward_active_energy_ = val;
 | 
			
		||||
  }
 | 
			
		||||
  // 0.01CF resolution = 0.003125 Wh per count
 | 
			
		||||
  return ((float) this->phase_[phase].cumulative_forward_active_energy_ * (10.0f / 3200.0f));
 | 
			
		||||
  return ((float) this->phase_[phase].cumulative_forward_active_energy_ * 10 / 3200);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_reverse_active_energy_(uint8_t phase) {
 | 
			
		||||
  const uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGY + phase);
 | 
			
		||||
  const uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGY);
 | 
			
		||||
  if (UINT32_MAX - this->phase_[phase].cumulative_reverse_active_energy_ > val) {
 | 
			
		||||
    this->phase_[phase].cumulative_reverse_active_energy_ += val;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->phase_[phase].cumulative_reverse_active_energy_ = val;
 | 
			
		||||
  }
 | 
			
		||||
  // 0.01CF resolution = 0.003125 Wh per count
 | 
			
		||||
  return ((float) this->phase_[phase].cumulative_reverse_active_energy_ * (10.0f / 3200.0f));
 | 
			
		||||
  return ((float) this->phase_[phase].cumulative_reverse_active_energy_ * 10 / 3200);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) {
 | 
			
		||||
@@ -419,15 +446,15 @@ float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) {
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_angle_(uint8_t phase) {
 | 
			
		||||
  uint16_t val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0;
 | 
			
		||||
  return (val > 180) ? (float) (val - 360.0f) : (float) val;
 | 
			
		||||
  return (float) (val > 180) ? val - 360.0 : val;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_peak_current_(uint8_t phase) {
 | 
			
		||||
  int16_t val = (float) this->read16_(ATM90E32_REGISTER_IPEAK + phase);
 | 
			
		||||
  if (!this->peak_current_signed_)
 | 
			
		||||
    val = std::abs(val);
 | 
			
		||||
    val = abs(val);
 | 
			
		||||
  // phase register * phase current gain value  / 1000 * 2^13
 | 
			
		||||
  return (val * this->phase_[phase].ct_gain_ / 8192000.0);
 | 
			
		||||
  return (float) (val * this->phase_[phase].ct_gain_ / 8192000.0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_frequency_() {
 | 
			
		||||
@@ -440,433 +467,29 @@ float ATM90E32Component::get_chip_temperature_() {
 | 
			
		||||
  return (float) ctemp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::run_gain_calibrations() {
 | 
			
		||||
  if (!this->enable_gain_calibration_) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] Gain calibration is disabled! Enable it first with enable_gain_calibration: true");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  float ref_voltages[3] = {
 | 
			
		||||
      this->get_reference_voltage(0),
 | 
			
		||||
      this->get_reference_voltage(1),
 | 
			
		||||
      this->get_reference_voltage(2),
 | 
			
		||||
  };
 | 
			
		||||
  float ref_currents[3] = {this->get_reference_current(0), this->get_reference_current(1),
 | 
			
		||||
                           this->get_reference_current(2)};
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] ");
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] ========================= Gain Calibration  =========================");
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
 | 
			
		||||
  ESP_LOGI(TAG,
 | 
			
		||||
           "[CALIBRATION] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref  | V_gain (old→new) | I_gain (old→new) |");
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
 | 
			
		||||
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
    float measured_voltage = this->get_phase_voltage_avg_(phase);
 | 
			
		||||
    float measured_current = this->get_phase_current_avg_(phase);
 | 
			
		||||
 | 
			
		||||
    float ref_voltage = ref_voltages[phase];
 | 
			
		||||
    float ref_current = ref_currents[phase];
 | 
			
		||||
 | 
			
		||||
    uint16_t current_voltage_gain = this->read16_(voltage_gain_registers[phase]);
 | 
			
		||||
    uint16_t current_current_gain = this->read16_(current_gain_registers[phase]);
 | 
			
		||||
 | 
			
		||||
    bool did_voltage = false;
 | 
			
		||||
    bool did_current = false;
 | 
			
		||||
 | 
			
		||||
    // Voltage calibration
 | 
			
		||||
    if (ref_voltage <= 0.0f) {
 | 
			
		||||
      ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: reference voltage is 0.",
 | 
			
		||||
               phase_labels[phase]);
 | 
			
		||||
    } else if (measured_voltage == 0.0f) {
 | 
			
		||||
      ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: measured voltage is 0.",
 | 
			
		||||
               phase_labels[phase]);
 | 
			
		||||
    } else {
 | 
			
		||||
      uint32_t new_voltage_gain = static_cast<uint16_t>((ref_voltage / measured_voltage) * current_voltage_gain);
 | 
			
		||||
      if (new_voltage_gain == 0) {
 | 
			
		||||
        ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Voltage gain would be 0. Check reference and measured voltage.",
 | 
			
		||||
                 phase_labels[phase]);
 | 
			
		||||
      } else {
 | 
			
		||||
        if (new_voltage_gain >= 65535) {
 | 
			
		||||
          ESP_LOGW(
 | 
			
		||||
              TAG,
 | 
			
		||||
              "[CALIBRATION] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage transformer.",
 | 
			
		||||
              phase_labels[phase]);
 | 
			
		||||
          new_voltage_gain = 65535;
 | 
			
		||||
        }
 | 
			
		||||
        this->gain_phase_[phase].voltage_gain = static_cast<uint16_t>(new_voltage_gain);
 | 
			
		||||
        did_voltage = true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Current calibration
 | 
			
		||||
    if (ref_current == 0.0f) {
 | 
			
		||||
      ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: reference current is 0.",
 | 
			
		||||
               phase_labels[phase]);
 | 
			
		||||
    } else if (measured_current == 0.0f) {
 | 
			
		||||
      ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: measured current is 0.",
 | 
			
		||||
               phase_labels[phase]);
 | 
			
		||||
    } else {
 | 
			
		||||
      uint32_t new_current_gain = static_cast<uint16_t>((ref_current / measured_current) * current_current_gain);
 | 
			
		||||
      if (new_current_gain == 0) {
 | 
			
		||||
        ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain would be 0. Check reference and measured current.",
 | 
			
		||||
                 phase_labels[phase]);
 | 
			
		||||
      } else {
 | 
			
		||||
        if (new_current_gain >= 65535) {
 | 
			
		||||
          ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
 | 
			
		||||
                   phase_labels[phase]);
 | 
			
		||||
          new_current_gain = 65535;
 | 
			
		||||
        }
 | 
			
		||||
        this->gain_phase_[phase].current_gain = static_cast<uint16_t>(new_current_gain);
 | 
			
		||||
        did_current = true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Final row output
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] |   %c   |  %9.2f |  %9.4f | %5.2f | %6.4f |  %5u → %-5u  |  %5u → %-5u  |",
 | 
			
		||||
             'A' + phase, measured_voltage, measured_current, ref_voltage, ref_current, current_voltage_gain,
 | 
			
		||||
             did_voltage ? this->gain_phase_[phase].voltage_gain : current_voltage_gain, current_current_gain,
 | 
			
		||||
             did_current ? this->gain_phase_[phase].current_gain : current_current_gain);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] =====================================================================\n");
 | 
			
		||||
 | 
			
		||||
  this->save_gain_calibration_to_memory_();
 | 
			
		||||
  this->write_gains_to_registers_();
 | 
			
		||||
  this->verify_gain_writes_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::save_gain_calibration_to_memory_() {
 | 
			
		||||
  bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
 | 
			
		||||
  if (success) {
 | 
			
		||||
    this->using_saved_calibrations_ = true;
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Gain calibration saved to memory.");
 | 
			
		||||
  } else {
 | 
			
		||||
    this->using_saved_calibrations_ = false;
 | 
			
		||||
    ESP_LOGE(TAG, "[CALIBRATION] Failed to save gain calibration to memory!");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::run_offset_calibrations() {
 | 
			
		||||
  if (!this->enable_offset_calibration_) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] Offset calibration is disabled! Enable it first with enable_offset_calibration: true");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
    int16_t voltage_offset = calibrate_offset(phase, true);
 | 
			
		||||
    int16_t current_offset = calibrate_offset(phase, false);
 | 
			
		||||
 | 
			
		||||
    this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
 | 
			
		||||
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage: %d, offset_current: %d", 'A' + phase, voltage_offset,
 | 
			
		||||
             current_offset);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->offset_pref_.save(&this->offset_phase_);  // Save to flash
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::run_power_offset_calibrations() {
 | 
			
		||||
  if (!this->enable_offset_calibration_) {
 | 
			
		||||
    ESP_LOGW(
 | 
			
		||||
        TAG,
 | 
			
		||||
        "[CALIBRATION] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; ++phase) {
 | 
			
		||||
    int16_t active_offset = calibrate_power_offset(phase, false);
 | 
			
		||||
    int16_t reactive_offset = calibrate_power_offset(phase, true);
 | 
			
		||||
 | 
			
		||||
    this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
 | 
			
		||||
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
 | 
			
		||||
             active_offset, reactive_offset);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->power_offset_pref_.save(&this->power_offset_phase_);  // Save to flash
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::write_gains_to_registers_() {
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);
 | 
			
		||||
 | 
			
		||||
  for (int phase = 0; phase < 3; phase++) {
 | 
			
		||||
    this->write16_(voltage_gain_registers[phase], this->gain_phase_[phase].voltage_gain);
 | 
			
		||||
    this->write16_(current_gain_registers[phase], this->gain_phase_[phase].current_gain);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset) {
 | 
			
		||||
  // Save to runtime
 | 
			
		||||
  this->offset_phase_[phase].voltage_offset_ = voltage_offset;
 | 
			
		||||
  this->phase_[phase].voltage_offset_ = voltage_offset;
 | 
			
		||||
 | 
			
		||||
  // Save to flash-storable struct
 | 
			
		||||
  this->offset_phase_[phase].current_offset_ = current_offset;
 | 
			
		||||
  this->phase_[phase].current_offset_ = current_offset;
 | 
			
		||||
 | 
			
		||||
  // Write to registers
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);
 | 
			
		||||
  this->write16_(voltage_offset_registers[phase], static_cast<uint16_t>(voltage_offset));
 | 
			
		||||
  this->write16_(current_offset_registers[phase], static_cast<uint16_t>(current_offset));
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset) {
 | 
			
		||||
  // Save to runtime
 | 
			
		||||
  this->phase_[phase].active_power_offset_ = p_offset;
 | 
			
		||||
  this->phase_[phase].reactive_power_offset_ = q_offset;
 | 
			
		||||
 | 
			
		||||
  // Save to flash-storable struct
 | 
			
		||||
  this->power_offset_phase_[phase].active_power_offset = p_offset;
 | 
			
		||||
  this->power_offset_phase_[phase].reactive_power_offset = q_offset;
 | 
			
		||||
 | 
			
		||||
  // Write to registers
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);
 | 
			
		||||
  this->write16_(this->power_offset_registers[phase], static_cast<uint16_t>(p_offset));
 | 
			
		||||
  this->write16_(this->reactive_power_offset_registers[phase], static_cast<uint16_t>(q_offset));
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::restore_gain_calibrations_() {
 | 
			
		||||
  if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Restoring saved gain calibrations to registers:");
 | 
			
		||||
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      uint16_t v_gain = this->gain_phase_[phase].voltage_gain;
 | 
			
		||||
      uint16_t i_gain = this->gain_phase_[phase].current_gain;
 | 
			
		||||
      ESP_LOGI(TAG, "[CALIBRATION]   Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase, v_gain, i_gain);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->write_gains_to_registers_();
 | 
			
		||||
 | 
			
		||||
    if (this->verify_gain_writes_()) {
 | 
			
		||||
      this->using_saved_calibrations_ = true;
 | 
			
		||||
      ESP_LOGI(TAG, "[CALIBRATION] Gain calibration loaded and verified successfully.");
 | 
			
		||||
    } else {
 | 
			
		||||
      this->using_saved_calibrations_ = false;
 | 
			
		||||
      ESP_LOGE(TAG, "[CALIBRATION] Gain verification failed! Calibration may not be applied correctly.");
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    this->using_saved_calibrations_ = false;
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No stored gain calibrations found. Using config file values.");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::restore_offset_calibrations_() {
 | 
			
		||||
  if (this->offset_pref_.load(&this->offset_phase_)) {
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Successfully restored offset calibration from memory.");
 | 
			
		||||
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      auto &offset = this->offset_phase_[phase];
 | 
			
		||||
      write_offsets_to_registers_(phase, offset.voltage_offset_, offset.current_offset_);
 | 
			
		||||
      ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage:: %d, offset_current: %d", 'A' + phase,
 | 
			
		||||
               offset.voltage_offset_, offset.current_offset_);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No stored offset calibrations found. Using default values.");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::restore_power_offset_calibrations_() {
 | 
			
		||||
  if (this->power_offset_pref_.load(&this->power_offset_phase_)) {
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Successfully restored power offset calibration from memory.");
 | 
			
		||||
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; ++phase) {
 | 
			
		||||
      auto &offset = this->power_offset_phase_[phase];
 | 
			
		||||
      write_power_offsets_to_registers_(phase, offset.active_power_offset, offset.reactive_power_offset);
 | 
			
		||||
      ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
 | 
			
		||||
               offset.active_power_offset, offset.reactive_power_offset);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No stored power offsets found. Using default values.");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::clear_gain_calibrations() {
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] Clearing stored gain calibrations and restoring config-defined values...");
 | 
			
		||||
 | 
			
		||||
  for (int phase = 0; phase < 3; phase++) {
 | 
			
		||||
    gain_phase_[phase].voltage_gain = this->phase_[phase].voltage_gain_;
 | 
			
		||||
    gain_phase_[phase].current_gain = this->phase_[phase].ct_gain_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
 | 
			
		||||
  this->using_saved_calibrations_ = false;
 | 
			
		||||
 | 
			
		||||
  if (success) {
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Gain calibrations cleared. Config values restored:");
 | 
			
		||||
    for (int phase = 0; phase < 3; phase++) {
 | 
			
		||||
      ESP_LOGI(TAG, "[CALIBRATION]   Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase,
 | 
			
		||||
               gain_phase_[phase].voltage_gain, gain_phase_[phase].current_gain);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGE(TAG, "[CALIBRATION] Failed to clear gain calibrations!");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->write_gains_to_registers_();  // Apply them to the chip immediately
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::clear_offset_calibrations() {
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
    this->write_offsets_to_registers_(phase, 0, 0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->offset_pref_.save(&this->offset_phase_);  // Save cleared values to flash memory
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] Offsets cleared.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::clear_power_offset_calibrations() {
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
    this->write_power_offsets_to_registers_(phase, 0, 0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->power_offset_pref_.save(&this->power_offset_phase_);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] Power offsets cleared.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
 | 
			
		||||
uint16_t ATM90E32Component::calibrate_voltage_offset_phase(uint8_t phase) {
 | 
			
		||||
  const uint8_t num_reads = 5;
 | 
			
		||||
  uint64_t total_value = 0;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t i = 0; i < num_reads; ++i) {
 | 
			
		||||
    uint32_t reading = voltage ? this->read32_(ATM90E32_REGISTER_URMS + phase, ATM90E32_REGISTER_URMSLSB + phase)
 | 
			
		||||
                               : this->read32_(ATM90E32_REGISTER_IRMS + phase, ATM90E32_REGISTER_IRMSLSB + phase);
 | 
			
		||||
    total_value += reading;
 | 
			
		||||
  for (int i = 0; i < num_reads; ++i) {
 | 
			
		||||
    const uint32_t measurement_value = read32_(ATM90E32_REGISTER_URMS + phase, ATM90E32_REGISTER_URMSLSB + phase);
 | 
			
		||||
    total_value += measurement_value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const uint32_t average_value = total_value / num_reads;
 | 
			
		||||
  const uint32_t shifted = average_value >> 7;
 | 
			
		||||
  const uint32_t offset = ~shifted + 1;
 | 
			
		||||
  return static_cast<int16_t>(offset);  // Takes lower 16 bits
 | 
			
		||||
  const uint32_t shifted_value = average_value >> 7;
 | 
			
		||||
  const uint32_t voltage_offset = ~shifted_value + 1;
 | 
			
		||||
  return voltage_offset & 0xFFFF;  // Take the lower 16 bits
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int16_t ATM90E32Component::calibrate_power_offset(uint8_t phase, bool reactive) {
 | 
			
		||||
uint16_t ATM90E32Component::calibrate_current_offset_phase(uint8_t phase) {
 | 
			
		||||
  const uint8_t num_reads = 5;
 | 
			
		||||
  uint64_t total_value = 0;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t i = 0; i < num_reads; ++i) {
 | 
			
		||||
    uint32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
 | 
			
		||||
                                : this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
 | 
			
		||||
    total_value += reading;
 | 
			
		||||
  for (int i = 0; i < num_reads; ++i) {
 | 
			
		||||
    const uint32_t measurement_value = read32_(ATM90E32_REGISTER_IRMS + phase, ATM90E32_REGISTER_IRMSLSB + phase);
 | 
			
		||||
    total_value += measurement_value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const uint32_t average_value = total_value / num_reads;
 | 
			
		||||
  const uint32_t power_offset = ~average_value + 1;
 | 
			
		||||
  return static_cast<int16_t>(power_offset);  // Takes the lower 16 bits
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ATM90E32Component::verify_gain_writes_() {
 | 
			
		||||
  bool success = true;
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
    uint16_t read_voltage = this->read16_(voltage_gain_registers[phase]);
 | 
			
		||||
    uint16_t read_current = this->read16_(current_gain_registers[phase]);
 | 
			
		||||
 | 
			
		||||
    if (read_voltage != this->gain_phase_[phase].voltage_gain ||
 | 
			
		||||
        read_current != this->gain_phase_[phase].current_gain) {
 | 
			
		||||
      ESP_LOGE(TAG, "[CALIBRATION] Mismatch detected for Phase %s!", phase_labels[phase]);
 | 
			
		||||
      success = false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return success;  // Return true if all writes were successful, false otherwise
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
void ATM90E32Component::check_phase_status() {
 | 
			
		||||
  uint16_t state0 = this->read16_(ATM90E32_REGISTER_EMMSTATE0);
 | 
			
		||||
  uint16_t state1 = this->read16_(ATM90E32_REGISTER_EMMSTATE1);
 | 
			
		||||
 | 
			
		||||
  for (int phase = 0; phase < 3; phase++) {
 | 
			
		||||
    std::string status;
 | 
			
		||||
 | 
			
		||||
    if (state0 & over_voltage_flags[phase])
 | 
			
		||||
      status += "Over Voltage; ";
 | 
			
		||||
    if (state1 & voltage_sag_flags[phase])
 | 
			
		||||
      status += "Voltage Sag; ";
 | 
			
		||||
    if (state1 & phase_loss_flags[phase])
 | 
			
		||||
      status += "Phase Loss; ";
 | 
			
		||||
 | 
			
		||||
    auto *sensor = this->phase_status_text_sensor_[phase];
 | 
			
		||||
    const char *phase_name = sensor ? sensor->get_name().c_str() : "Unknown Phase";
 | 
			
		||||
    if (!status.empty()) {
 | 
			
		||||
      status.pop_back();  // remove space
 | 
			
		||||
      status.pop_back();  // remove semicolon
 | 
			
		||||
      ESP_LOGW(TAG, "%s: %s", phase_name, status.c_str());
 | 
			
		||||
      if (sensor != nullptr)
 | 
			
		||||
        sensor->publish_state(status);
 | 
			
		||||
    } else {
 | 
			
		||||
      if (sensor != nullptr)
 | 
			
		||||
        sensor->publish_state("Okay");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::check_freq_status() {
 | 
			
		||||
  uint16_t state1 = this->read16_(ATM90E32_REGISTER_EMMSTATE1);
 | 
			
		||||
 | 
			
		||||
  std::string freq_status;
 | 
			
		||||
 | 
			
		||||
  if (state1 & ATM90E32_STATUS_S1_FREQHIST) {
 | 
			
		||||
    freq_status = "HIGH";
 | 
			
		||||
  } else if (state1 & ATM90E32_STATUS_S1_FREQLOST) {
 | 
			
		||||
    freq_status = "LOW";
 | 
			
		||||
  } else {
 | 
			
		||||
    freq_status = "Normal";
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
 | 
			
		||||
 | 
			
		||||
  if (this->freq_status_text_sensor_ != nullptr) {
 | 
			
		||||
    this->freq_status_text_sensor_->publish_state(freq_status);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::check_over_current() {
 | 
			
		||||
  constexpr float max_current_threshold = 65.53f;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
    float current_val =
 | 
			
		||||
        this->phase_[phase].current_sensor_ != nullptr ? this->phase_[phase].current_sensor_->state : 0.0f;
 | 
			
		||||
 | 
			
		||||
    if (current_val > max_current_threshold) {
 | 
			
		||||
      ESP_LOGW(TAG, "Over current detected on Phase %c: %.2f A", 'A' + phase, current_val);
 | 
			
		||||
      ESP_LOGW(TAG, "You may need to half your gain_ct: value & multiply the current and power values by 2");
 | 
			
		||||
      if (this->phase_status_text_sensor_[phase] != nullptr) {
 | 
			
		||||
        this->phase_status_text_sensor_[phase]->publish_state("Over Current; ");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
uint16_t ATM90E32Component::calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier) {
 | 
			
		||||
  // this assumes that 60Hz electrical systems use 120V mains,
 | 
			
		||||
  // which is usually, but not always the case
 | 
			
		||||
  float nominal_voltage = (line_freq == 60) ? 120.0f : 220.0f;
 | 
			
		||||
  float target_voltage = nominal_voltage * multiplier;
 | 
			
		||||
 | 
			
		||||
  float peak_01v = target_voltage * 100.0f * std::sqrt(2.0f);  // convert RMS → peak, scale to 0.01V
 | 
			
		||||
  float divider = (2.0f * ugain) / 32768.0f;
 | 
			
		||||
 | 
			
		||||
  float threshold = peak_01v / divider;
 | 
			
		||||
 | 
			
		||||
  return static_cast<uint16_t>(threshold);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ATM90E32Component::validate_spi_read_(uint16_t expected, const char *context) {
 | 
			
		||||
  uint16_t last = this->read16_(ATM90E32_REGISTER_LASTSPIDATA);
 | 
			
		||||
  if (last != expected) {
 | 
			
		||||
    if (context != nullptr) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] SPI read mismatch: expected 0x%04X, got 0x%04X", context, expected, last);
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGW(TAG, "SPI read mismatch: expected 0x%04X, got 0x%04X", expected, last);
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
  const uint32_t current_offset = ~average_value + 1;
 | 
			
		||||
  return current_offset & 0xFFFF;  // Take the lower 16 bits
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace atm90e32
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include "atm90e32_reg.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/spi/spi.h"
 | 
			
		||||
@@ -19,26 +18,6 @@ class ATM90E32Component : public PollingComponent,
 | 
			
		||||
  static const uint8_t PHASEA = 0;
 | 
			
		||||
  static const uint8_t PHASEB = 1;
 | 
			
		||||
  static const uint8_t PHASEC = 2;
 | 
			
		||||
  const char *phase_labels[3] = {"A", "B", "C"};
 | 
			
		||||
  // these registers are not sucessive, so we can't just do 'base + phase'
 | 
			
		||||
  const uint16_t voltage_gain_registers[3] = {ATM90E32_REGISTER_UGAINA, ATM90E32_REGISTER_UGAINB,
 | 
			
		||||
                                              ATM90E32_REGISTER_UGAINC};
 | 
			
		||||
  const uint16_t current_gain_registers[3] = {ATM90E32_REGISTER_IGAINA, ATM90E32_REGISTER_IGAINB,
 | 
			
		||||
                                              ATM90E32_REGISTER_IGAINC};
 | 
			
		||||
  const uint16_t voltage_offset_registers[3] = {ATM90E32_REGISTER_UOFFSETA, ATM90E32_REGISTER_UOFFSETB,
 | 
			
		||||
                                                ATM90E32_REGISTER_UOFFSETC};
 | 
			
		||||
  const uint16_t current_offset_registers[3] = {ATM90E32_REGISTER_IOFFSETA, ATM90E32_REGISTER_IOFFSETB,
 | 
			
		||||
                                                ATM90E32_REGISTER_IOFFSETC};
 | 
			
		||||
  const uint16_t power_offset_registers[3] = {ATM90E32_REGISTER_POFFSETA, ATM90E32_REGISTER_POFFSETB,
 | 
			
		||||
                                              ATM90E32_REGISTER_POFFSETC};
 | 
			
		||||
  const uint16_t reactive_power_offset_registers[3] = {ATM90E32_REGISTER_QOFFSETA, ATM90E32_REGISTER_QOFFSETB,
 | 
			
		||||
                                                       ATM90E32_REGISTER_QOFFSETC};
 | 
			
		||||
  const uint16_t over_voltage_flags[3] = {ATM90E32_STATUS_S0_OVPHASEAST, ATM90E32_STATUS_S0_OVPHASEBST,
 | 
			
		||||
                                          ATM90E32_STATUS_S0_OVPHASECST};
 | 
			
		||||
  const uint16_t voltage_sag_flags[3] = {ATM90E32_STATUS_S1_SAGPHASEAST, ATM90E32_STATUS_S1_SAGPHASEBST,
 | 
			
		||||
                                         ATM90E32_STATUS_S1_SAGPHASECST};
 | 
			
		||||
  const uint16_t phase_loss_flags[3] = {ATM90E32_STATUS_S1_PHASELOSSAST, ATM90E32_STATUS_S1_PHASELOSSBST,
 | 
			
		||||
                                        ATM90E32_STATUS_S1_PHASELOSSCST};
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
@@ -63,14 +42,6 @@ class ATM90E32Component : public PollingComponent,
 | 
			
		||||
  void set_peak_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].peak_current_sensor_ = obj; }
 | 
			
		||||
  void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].voltage_gain_ = gain; }
 | 
			
		||||
  void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
 | 
			
		||||
  void set_voltage_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].voltage_offset_ = offset; }
 | 
			
		||||
  void set_current_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].current_offset_ = offset; }
 | 
			
		||||
  void set_active_power_offset(uint8_t phase, int16_t offset) {
 | 
			
		||||
    this->power_offset_phase_[phase].active_power_offset = offset;
 | 
			
		||||
  }
 | 
			
		||||
  void set_reactive_power_offset(uint8_t phase, int16_t offset) {
 | 
			
		||||
    this->power_offset_phase_[phase].reactive_power_offset = offset;
 | 
			
		||||
  }
 | 
			
		||||
  void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
 | 
			
		||||
  void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; }
 | 
			
		||||
  void set_chip_temperature_sensor(sensor::Sensor *chip_temperature_sensor) {
 | 
			
		||||
@@ -80,104 +51,53 @@ class ATM90E32Component : public PollingComponent,
 | 
			
		||||
  void set_current_phases(int phases) { current_phases_ = phases; }
 | 
			
		||||
  void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
 | 
			
		||||
  void run_offset_calibrations();
 | 
			
		||||
  void run_power_offset_calibrations();
 | 
			
		||||
  void clear_offset_calibrations();
 | 
			
		||||
  void clear_power_offset_calibrations();
 | 
			
		||||
  void clear_gain_calibrations();
 | 
			
		||||
  void set_enable_offset_calibration(bool flag) { enable_offset_calibration_ = flag; }
 | 
			
		||||
  void set_enable_gain_calibration(bool flag) { enable_gain_calibration_ = flag; }
 | 
			
		||||
  int16_t calibrate_offset(uint8_t phase, bool voltage);
 | 
			
		||||
  int16_t calibrate_power_offset(uint8_t phase, bool reactive);
 | 
			
		||||
  void run_gain_calibrations();
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  void set_reference_voltage(uint8_t phase, number::Number *ref_voltage) { ref_voltages_[phase] = ref_voltage; }
 | 
			
		||||
  void set_reference_current(uint8_t phase, number::Number *ref_current) { ref_currents_[phase] = ref_current; }
 | 
			
		||||
#endif
 | 
			
		||||
  float get_reference_voltage(uint8_t phase) {
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
    return (phase >= 0 && phase < 3 && ref_voltages_[phase]) ? ref_voltages_[phase]->state : 120.0;  // Default voltage
 | 
			
		||||
#else
 | 
			
		||||
    return 120.0;  // Default voltage
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
  float get_reference_current(uint8_t phase) {
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
    return (phase >= 0 && phase < 3 && ref_currents_[phase]) ? ref_currents_[phase]->state : 5.0f;  // Default current
 | 
			
		||||
#else
 | 
			
		||||
    return 5.0f;   // Default current
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
  bool using_saved_calibrations_ = false;  // Track if stored calibrations are being used
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  void check_phase_status();
 | 
			
		||||
  void check_freq_status();
 | 
			
		||||
  void check_over_current();
 | 
			
		||||
  void set_phase_status_text_sensor(uint8_t phase, text_sensor::TextSensor *sensor) {
 | 
			
		||||
    this->phase_status_text_sensor_[phase] = sensor;
 | 
			
		||||
  }
 | 
			
		||||
  void set_freq_status_text_sensor(text_sensor::TextSensor *sensor) { this->freq_status_text_sensor_ = sensor; }
 | 
			
		||||
#endif
 | 
			
		||||
  uint16_t calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier);
 | 
			
		||||
  uint16_t calibrate_voltage_offset_phase(uint8_t /*phase*/);
 | 
			
		||||
  uint16_t calibrate_current_offset_phase(uint8_t /*phase*/);
 | 
			
		||||
  int32_t last_periodic_millis = millis();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  number::Number *ref_voltages_[3]{nullptr, nullptr, nullptr};
 | 
			
		||||
  number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
 | 
			
		||||
#endif
 | 
			
		||||
  uint16_t read16_(uint16_t a_register);
 | 
			
		||||
  int read32_(uint16_t addr_h, uint16_t addr_l);
 | 
			
		||||
  void write16_(uint16_t a_register, uint16_t val);
 | 
			
		||||
  float get_local_phase_voltage_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_current_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_active_power_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_reactive_power_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_apparent_power_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_power_factor_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_forward_active_energy_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_reverse_active_energy_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_angle_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_harmonic_active_power_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_peak_current_(uint8_t phase);
 | 
			
		||||
  float get_phase_voltage_(uint8_t phase);
 | 
			
		||||
  float get_phase_voltage_avg_(uint8_t phase);
 | 
			
		||||
  float get_phase_current_(uint8_t phase);
 | 
			
		||||
  float get_phase_current_avg_(uint8_t phase);
 | 
			
		||||
  float get_phase_active_power_(uint8_t phase);
 | 
			
		||||
  float get_phase_reactive_power_(uint8_t phase);
 | 
			
		||||
  float get_phase_apparent_power_(uint8_t phase);
 | 
			
		||||
  float get_phase_power_factor_(uint8_t phase);
 | 
			
		||||
  float get_phase_forward_active_energy_(uint8_t phase);
 | 
			
		||||
  float get_phase_reverse_active_energy_(uint8_t phase);
 | 
			
		||||
  float get_phase_angle_(uint8_t phase);
 | 
			
		||||
  float get_phase_harmonic_active_power_(uint8_t phase);
 | 
			
		||||
  float get_phase_peak_current_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_voltage_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_current_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_active_power_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_reactive_power_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_power_factor_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_forward_active_energy_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_reverse_active_energy_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_angle_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_harmonic_active_power_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_peak_current_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_voltage_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_voltage_avg_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_current_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_current_avg_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_active_power_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_reactive_power_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_power_factor_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_forward_active_energy_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_reverse_active_energy_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_angle_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_harmonic_active_power_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_peak_current_(uint8_t /*phase*/);
 | 
			
		||||
  float get_frequency_();
 | 
			
		||||
  float get_chip_temperature_();
 | 
			
		||||
  bool get_publish_interval_flag_() { return publish_interval_flag_; };
 | 
			
		||||
  void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; };
 | 
			
		||||
  void restore_offset_calibrations_();
 | 
			
		||||
  void restore_power_offset_calibrations_();
 | 
			
		||||
  void restore_gain_calibrations_();
 | 
			
		||||
  void save_gain_calibration_to_memory_();
 | 
			
		||||
  void write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset);
 | 
			
		||||
  void write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset);
 | 
			
		||||
  void write_gains_to_registers_();
 | 
			
		||||
  bool verify_gain_writes_();
 | 
			
		||||
  bool validate_spi_read_(uint16_t expected, const char *context = nullptr);
 | 
			
		||||
  void restore_calibrations_();
 | 
			
		||||
 | 
			
		||||
  struct ATM90E32Phase {
 | 
			
		||||
    uint16_t voltage_gain_{0};
 | 
			
		||||
    uint16_t ct_gain_{0};
 | 
			
		||||
    int16_t voltage_offset_{0};
 | 
			
		||||
    int16_t current_offset_{0};
 | 
			
		||||
    int16_t active_power_offset_{0};
 | 
			
		||||
    int16_t reactive_power_offset_{0};
 | 
			
		||||
    uint16_t voltage_offset_{0};
 | 
			
		||||
    uint16_t current_offset_{0};
 | 
			
		||||
    float voltage_{0};
 | 
			
		||||
    float current_{0};
 | 
			
		||||
    float active_power_{0};
 | 
			
		||||
    float reactive_power_{0};
 | 
			
		||||
    float apparent_power_{0};
 | 
			
		||||
    float power_factor_{0};
 | 
			
		||||
    float forward_active_energy_{0};
 | 
			
		||||
    float reverse_active_energy_{0};
 | 
			
		||||
@@ -199,30 +119,14 @@ class ATM90E32Component : public PollingComponent,
 | 
			
		||||
    uint32_t cumulative_reverse_active_energy_{0};
 | 
			
		||||
  } phase_[3];
 | 
			
		||||
 | 
			
		||||
  struct OffsetCalibration {
 | 
			
		||||
    int16_t voltage_offset_{0};
 | 
			
		||||
    int16_t current_offset_{0};
 | 
			
		||||
  struct Calibration {
 | 
			
		||||
    uint16_t voltage_offset_{0};
 | 
			
		||||
    uint16_t current_offset_{0};
 | 
			
		||||
  } offset_phase_[3];
 | 
			
		||||
 | 
			
		||||
  struct PowerOffsetCalibration {
 | 
			
		||||
    int16_t active_power_offset{0};
 | 
			
		||||
    int16_t reactive_power_offset{0};
 | 
			
		||||
  } power_offset_phase_[3];
 | 
			
		||||
 | 
			
		||||
  struct GainCalibration {
 | 
			
		||||
    uint16_t voltage_gain{1};
 | 
			
		||||
    uint16_t current_gain{1};
 | 
			
		||||
  } gain_phase_[3];
 | 
			
		||||
 | 
			
		||||
  ESPPreferenceObject offset_pref_;
 | 
			
		||||
  ESPPreferenceObject power_offset_pref_;
 | 
			
		||||
  ESPPreferenceObject gain_calibration_pref_;
 | 
			
		||||
  ESPPreferenceObject pref_;
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *freq_sensor_{nullptr};
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  text_sensor::TextSensor *phase_status_text_sensor_[3]{nullptr};
 | 
			
		||||
  text_sensor::TextSensor *freq_status_text_sensor_{nullptr};
 | 
			
		||||
#endif
 | 
			
		||||
  sensor::Sensor *chip_temperature_sensor_{nullptr};
 | 
			
		||||
  uint16_t pga_gain_{0x15};
 | 
			
		||||
  int line_freq_{60};
 | 
			
		||||
@@ -230,7 +134,6 @@ class ATM90E32Component : public PollingComponent,
 | 
			
		||||
  bool publish_interval_flag_{false};
 | 
			
		||||
  bool peak_current_signed_{false};
 | 
			
		||||
  bool enable_offset_calibration_{false};
 | 
			
		||||
  bool enable_gain_calibration_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace atm90e32
 | 
			
		||||
 
 | 
			
		||||
@@ -176,17 +176,16 @@ static const uint16_t ATM90E32_REGISTER_ANENERGYCH = 0xAF;  // C Reverse Harm. E
 | 
			
		||||
 | 
			
		||||
/* POWER & P.F. REGISTERS */
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_PMEANT = 0xB0;   // Total Mean Power (P)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_PMEAN = 0xB1;    // Active Power Reg Base (P)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_PMEAN = 0xB1;    // Mean Power Reg Base (P)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_PMEANA = 0xB1;   // A Mean Power (P)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_PMEANB = 0xB2;   // B Mean Power (P)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_PMEANC = 0xB3;   // C Mean Power (P)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEANT = 0xB4;   // Total Mean Power (Q)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEAN = 0xB5;    // Reactive Power Reg Base (Q)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEAN = 0xB5;    // Mean Power Reg Base (Q)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEANA = 0xB5;   // A Mean Power (Q)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEANB = 0xB6;   // B Mean Power (Q)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEANC = 0xB7;   // C Mean Power (Q)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANT = 0xB8;   // Total Mean Power (S)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEAN = 0xB9;    // Apparent Mean Power Base (S)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANA = 0xB9;   // A Mean Power (S)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANB = 0xBA;   // B Mean Power (S)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANC = 0xBB;   // C Mean Power (S)
 | 
			
		||||
@@ -207,7 +206,6 @@ static const uint16_t ATM90E32_REGISTER_QMEANALSB = 0xC5;   // Lower Word (A Rea
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEANBLSB = 0xC6;   // Lower Word (B React. Power)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEANCLSB = 0xC7;   // Lower Word (C React. Power)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SAMEANTLSB = 0xC8;  // Lower Word (Tot. App. Power)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANLSB = 0xC9;    // Lower Word Reg Base (Apparent Power)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANALSB = 0xC9;   // Lower Word (A App. Power)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANBLSB = 0xCA;   // Lower Word (B App. Power)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANCLSB = 0xCB;   // Lower Word (C App. Power)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,95 +1,43 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import button
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_SCALE
 | 
			
		||||
from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_CHIP, ICON_SCALE
 | 
			
		||||
 | 
			
		||||
from .. import atm90e32_ns
 | 
			
		||||
from ..sensor import ATM90E32Component
 | 
			
		||||
 | 
			
		||||
CONF_RUN_GAIN_CALIBRATION = "run_gain_calibration"
 | 
			
		||||
CONF_CLEAR_GAIN_CALIBRATION = "clear_gain_calibration"
 | 
			
		||||
CONF_RUN_OFFSET_CALIBRATION = "run_offset_calibration"
 | 
			
		||||
CONF_CLEAR_OFFSET_CALIBRATION = "clear_offset_calibration"
 | 
			
		||||
CONF_RUN_POWER_OFFSET_CALIBRATION = "run_power_offset_calibration"
 | 
			
		||||
CONF_CLEAR_POWER_OFFSET_CALIBRATION = "clear_power_offset_calibration"
 | 
			
		||||
 | 
			
		||||
ATM90E32GainCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32GainCalibrationButton", button.Button
 | 
			
		||||
ATM90E32CalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32CalibrationButton",
 | 
			
		||||
    button.Button,
 | 
			
		||||
)
 | 
			
		||||
ATM90E32ClearGainCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32ClearGainCalibrationButton", button.Button
 | 
			
		||||
)
 | 
			
		||||
ATM90E32OffsetCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32OffsetCalibrationButton", button.Button
 | 
			
		||||
)
 | 
			
		||||
ATM90E32ClearOffsetCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32ClearOffsetCalibrationButton", button.Button
 | 
			
		||||
)
 | 
			
		||||
ATM90E32PowerOffsetCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32PowerOffsetCalibrationButton", button.Button
 | 
			
		||||
)
 | 
			
		||||
ATM90E32ClearPowerOffsetCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32ClearPowerOffsetCalibrationButton", button.Button
 | 
			
		||||
ATM90E32ClearCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32ClearCalibrationButton",
 | 
			
		||||
    button.Button,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = {
 | 
			
		||||
    cv.GenerateID(CONF_ID): cv.use_id(ATM90E32Component),
 | 
			
		||||
    cv.Optional(CONF_RUN_GAIN_CALIBRATION): button.button_schema(
 | 
			
		||||
        ATM90E32GainCalibrationButton,
 | 
			
		||||
        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
        icon="mdi:scale-balance",
 | 
			
		||||
    ),
 | 
			
		||||
    cv.Optional(CONF_CLEAR_GAIN_CALIBRATION): button.button_schema(
 | 
			
		||||
        ATM90E32ClearGainCalibrationButton,
 | 
			
		||||
        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
        icon="mdi:delete",
 | 
			
		||||
    ),
 | 
			
		||||
    cv.Optional(CONF_RUN_OFFSET_CALIBRATION): button.button_schema(
 | 
			
		||||
        ATM90E32OffsetCalibrationButton,
 | 
			
		||||
        ATM90E32CalibrationButton,
 | 
			
		||||
        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
        icon=ICON_SCALE,
 | 
			
		||||
    ),
 | 
			
		||||
    cv.Optional(CONF_CLEAR_OFFSET_CALIBRATION): button.button_schema(
 | 
			
		||||
        ATM90E32ClearOffsetCalibrationButton,
 | 
			
		||||
        ATM90E32ClearCalibrationButton,
 | 
			
		||||
        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
        icon="mdi:delete",
 | 
			
		||||
    ),
 | 
			
		||||
    cv.Optional(CONF_RUN_POWER_OFFSET_CALIBRATION): button.button_schema(
 | 
			
		||||
        ATM90E32PowerOffsetCalibrationButton,
 | 
			
		||||
        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
        icon=ICON_SCALE,
 | 
			
		||||
    ),
 | 
			
		||||
    cv.Optional(CONF_CLEAR_POWER_OFFSET_CALIBRATION): button.button_schema(
 | 
			
		||||
        ATM90E32ClearPowerOffsetCalibrationButton,
 | 
			
		||||
        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
        icon="mdi:delete",
 | 
			
		||||
        icon=ICON_CHIP,
 | 
			
		||||
    ),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
 | 
			
		||||
    if run_gain := config.get(CONF_RUN_GAIN_CALIBRATION):
 | 
			
		||||
        b = await button.new_button(run_gain)
 | 
			
		||||
        await cg.register_parented(b, parent)
 | 
			
		||||
 | 
			
		||||
    if clear_gain := config.get(CONF_CLEAR_GAIN_CALIBRATION):
 | 
			
		||||
        b = await button.new_button(clear_gain)
 | 
			
		||||
        await cg.register_parented(b, parent)
 | 
			
		||||
 | 
			
		||||
    if run_offset := config.get(CONF_RUN_OFFSET_CALIBRATION):
 | 
			
		||||
        b = await button.new_button(run_offset)
 | 
			
		||||
        await cg.register_parented(b, parent)
 | 
			
		||||
 | 
			
		||||
    if clear_offset := config.get(CONF_CLEAR_OFFSET_CALIBRATION):
 | 
			
		||||
        b = await button.new_button(clear_offset)
 | 
			
		||||
        await cg.register_parented(b, parent)
 | 
			
		||||
 | 
			
		||||
    if run_power := config.get(CONF_RUN_POWER_OFFSET_CALIBRATION):
 | 
			
		||||
        b = await button.new_button(run_power)
 | 
			
		||||
        await cg.register_parented(b, parent)
 | 
			
		||||
 | 
			
		||||
    if clear_power := config.get(CONF_CLEAR_POWER_OFFSET_CALIBRATION):
 | 
			
		||||
        b = await button.new_button(clear_power)
 | 
			
		||||
        await cg.register_parented(b, parent)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
#include "atm90e32_button.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -7,73 +6,15 @@ namespace atm90e32 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "atm90e32.button";
 | 
			
		||||
 | 
			
		||||
void ATM90E32GainCalibrationButton::press_action() {
 | 
			
		||||
  if (this->parent_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Gain Calibration button [%s]", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "%s", this->get_name().c_str());
 | 
			
		||||
  ESP_LOGI(TAG,
 | 
			
		||||
           "[CALIBRATION] Use gain_ct: & gain_voltage: under each phase_x: in your config file to save these values");
 | 
			
		||||
  this->parent_->run_gain_calibrations();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32ClearGainCalibrationButton::press_action() {
 | 
			
		||||
  if (this->parent_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Clear Gain button [%s]", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "%s", this->get_name().c_str());
 | 
			
		||||
  this->parent_->clear_gain_calibrations();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32OffsetCalibrationButton::press_action() {
 | 
			
		||||
  if (this->parent_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Offset Calibration button [%s]", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "%s", this->get_name().c_str());
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] **NOTE: CTs and ACVs must be 0 during this process. USB power only**");
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] Use offset_voltage: & offset_current: under each phase_x: in your config file to save "
 | 
			
		||||
                "these values");
 | 
			
		||||
void ATM90E32CalibrationButton::press_action() {
 | 
			
		||||
  ESP_LOGI(TAG, "Running offset calibrations, Note: CTs and ACVs must be 0 during this process...");
 | 
			
		||||
  this->parent_->run_offset_calibrations();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32ClearOffsetCalibrationButton::press_action() {
 | 
			
		||||
  if (this->parent_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Clear Offset button [%s]", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "%s", this->get_name().c_str());
 | 
			
		||||
void ATM90E32ClearCalibrationButton::press_action() {
 | 
			
		||||
  ESP_LOGI(TAG, "Offset calibrations cleared.");
 | 
			
		||||
  this->parent_->clear_offset_calibrations();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32PowerOffsetCalibrationButton::press_action() {
 | 
			
		||||
  if (this->parent_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Power Calibration button [%s]", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "%s", this->get_name().c_str());
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] **NOTE: CTs must be 0 during this process. Voltage reference should be present**");
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] Use offset_active_power: & offset_reactive_power: under each phase_x: in your config "
 | 
			
		||||
                "file to save these values");
 | 
			
		||||
  this->parent_->run_power_offset_calibrations();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32ClearPowerOffsetCalibrationButton::press_action() {
 | 
			
		||||
  if (this->parent_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Clear Power button [%s]", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "%s", this->get_name().c_str());
 | 
			
		||||
  this->parent_->clear_power_offset_calibrations();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace atm90e32
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -7,49 +7,17 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atm90e32 {
 | 
			
		||||
 | 
			
		||||
class ATM90E32GainCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
class ATM90E32CalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  ATM90E32GainCalibrationButton() = default;
 | 
			
		||||
  ATM90E32CalibrationButton() = default;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void press_action() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ATM90E32ClearGainCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
class ATM90E32ClearCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  ATM90E32ClearGainCalibrationButton() = default;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void press_action() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ATM90E32OffsetCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  ATM90E32OffsetCalibrationButton() = default;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void press_action() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ATM90E32ClearOffsetCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  ATM90E32ClearOffsetCalibrationButton() = default;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void press_action() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ATM90E32PowerOffsetCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  ATM90E32PowerOffsetCalibrationButton() = default;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void press_action() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ATM90E32ClearPowerOffsetCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  ATM90E32ClearPowerOffsetCalibrationButton() = default;
 | 
			
		||||
  ATM90E32ClearCalibrationButton() = default;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void press_action() override;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,130 +0,0 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import number
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MAX_VALUE,
 | 
			
		||||
    CONF_MIN_VALUE,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_PHASE_A,
 | 
			
		||||
    CONF_PHASE_B,
 | 
			
		||||
    CONF_PHASE_C,
 | 
			
		||||
    CONF_REFERENCE_VOLTAGE,
 | 
			
		||||
    CONF_STEP,
 | 
			
		||||
    ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from .. import atm90e32_ns
 | 
			
		||||
from ..sensor import ATM90E32Component
 | 
			
		||||
 | 
			
		||||
ATM90E32Number = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32Number", number.Number, cg.Parented.template(ATM90E32Component)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_REFERENCE_CURRENT = "reference_current"
 | 
			
		||||
PHASE_KEYS = [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
REFERENCE_VOLTAGE_PHASE_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_MODE, default="box"): cv.string,
 | 
			
		||||
            cv.Optional(CONF_MIN_VALUE, default=100.0): cv.float_,
 | 
			
		||||
            cv.Optional(CONF_MAX_VALUE, default=260.0): cv.float_,
 | 
			
		||||
            cv.Optional(CONF_STEP, default=0.1): cv.float_,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(
 | 
			
		||||
        number.number_schema(
 | 
			
		||||
            class_=ATM90E32Number,
 | 
			
		||||
            unit_of_measurement=UNIT_VOLT,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
            icon="mdi:power-plug",
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
REFERENCE_CURRENT_PHASE_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_MODE, default="box"): cv.string,
 | 
			
		||||
            cv.Optional(CONF_MIN_VALUE, default=1.0): cv.float_,
 | 
			
		||||
            cv.Optional(CONF_MAX_VALUE, default=200.0): cv.float_,
 | 
			
		||||
            cv.Optional(CONF_STEP, default=0.1): cv.float_,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(
 | 
			
		||||
        number.number_schema(
 | 
			
		||||
            class_=ATM90E32Number,
 | 
			
		||||
            unit_of_measurement=UNIT_AMPERE,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
            icon="mdi:home-lightning-bolt",
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
REFERENCE_VOLTAGE_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_PHASE_A): REFERENCE_VOLTAGE_PHASE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_PHASE_B): REFERENCE_VOLTAGE_PHASE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_PHASE_C): REFERENCE_VOLTAGE_PHASE_SCHEMA,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
REFERENCE_CURRENT_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_PHASE_A): REFERENCE_CURRENT_PHASE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_PHASE_B): REFERENCE_CURRENT_PHASE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_PHASE_C): REFERENCE_CURRENT_PHASE_SCHEMA,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_ID): cv.use_id(ATM90E32Component),
 | 
			
		||||
        cv.Optional(CONF_REFERENCE_VOLTAGE): REFERENCE_VOLTAGE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_REFERENCE_CURRENT): REFERENCE_CURRENT_SCHEMA,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
 | 
			
		||||
    if voltage_cfg := config.get(CONF_REFERENCE_VOLTAGE):
 | 
			
		||||
        voltage_objs = [None, None, None]
 | 
			
		||||
 | 
			
		||||
        for i, key in enumerate(PHASE_KEYS):
 | 
			
		||||
            if validated := voltage_cfg.get(key):
 | 
			
		||||
                obj = await number.new_number(
 | 
			
		||||
                    validated,
 | 
			
		||||
                    min_value=validated["min_value"],
 | 
			
		||||
                    max_value=validated["max_value"],
 | 
			
		||||
                    step=validated["step"],
 | 
			
		||||
                )
 | 
			
		||||
                await cg.register_parented(obj, parent)
 | 
			
		||||
                voltage_objs[i] = obj
 | 
			
		||||
 | 
			
		||||
        # Inherit from A → B/C if only A defined
 | 
			
		||||
        if voltage_objs[0] is not None:
 | 
			
		||||
            for i in range(3):
 | 
			
		||||
                if voltage_objs[i] is None:
 | 
			
		||||
                    voltage_objs[i] = voltage_objs[0]
 | 
			
		||||
 | 
			
		||||
        for i, obj in enumerate(voltage_objs):
 | 
			
		||||
            if obj is not None:
 | 
			
		||||
                cg.add(parent.set_reference_voltage(i, obj))
 | 
			
		||||
 | 
			
		||||
    if current_cfg := config.get(CONF_REFERENCE_CURRENT):
 | 
			
		||||
        for i, key in enumerate(PHASE_KEYS):
 | 
			
		||||
            if validated := current_cfg.get(key):
 | 
			
		||||
                obj = await number.new_number(
 | 
			
		||||
                    validated,
 | 
			
		||||
                    min_value=validated["min_value"],
 | 
			
		||||
                    max_value=validated["max_value"],
 | 
			
		||||
                    step=validated["step"],
 | 
			
		||||
                )
 | 
			
		||||
                await cg.register_parented(obj, parent)
 | 
			
		||||
                cg.add(parent.set_reference_current(i, obj))
 | 
			
		||||
@@ -1,16 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/atm90e32/atm90e32.h"
 | 
			
		||||
#include "esphome/components/number/number.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atm90e32 {
 | 
			
		||||
 | 
			
		||||
class ATM90E32Number : public number::Number, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  void control(float value) override { this->publish_state(value); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace atm90e32
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -33,7 +33,6 @@ from esphome.const import (
 | 
			
		||||
    UNIT_DEGREES,
 | 
			
		||||
    UNIT_HERTZ,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_VOLT_AMPS,
 | 
			
		||||
    UNIT_VOLT_AMPS_REACTIVE,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
    UNIT_WATT_HOURS,
 | 
			
		||||
@@ -46,17 +45,10 @@ CONF_GAIN_PGA = "gain_pga"
 | 
			
		||||
CONF_CURRENT_PHASES = "current_phases"
 | 
			
		||||
CONF_GAIN_VOLTAGE = "gain_voltage"
 | 
			
		||||
CONF_GAIN_CT = "gain_ct"
 | 
			
		||||
CONF_OFFSET_VOLTAGE = "offset_voltage"
 | 
			
		||||
CONF_OFFSET_CURRENT = "offset_current"
 | 
			
		||||
CONF_OFFSET_ACTIVE_POWER = "offset_active_power"
 | 
			
		||||
CONF_OFFSET_REACTIVE_POWER = "offset_reactive_power"
 | 
			
		||||
CONF_HARMONIC_POWER = "harmonic_power"
 | 
			
		||||
CONF_PEAK_CURRENT = "peak_current"
 | 
			
		||||
CONF_PEAK_CURRENT_SIGNED = "peak_current_signed"
 | 
			
		||||
CONF_ENABLE_OFFSET_CALIBRATION = "enable_offset_calibration"
 | 
			
		||||
CONF_ENABLE_GAIN_CALIBRATION = "enable_gain_calibration"
 | 
			
		||||
CONF_PHASE_STATUS = "phase_status"
 | 
			
		||||
CONF_FREQUENCY_STATUS = "frequency_status"
 | 
			
		||||
UNIT_DEG = "degrees"
 | 
			
		||||
LINE_FREQS = {
 | 
			
		||||
    "50HZ": 50,
 | 
			
		||||
@@ -100,11 +92,10 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
 | 
			
		||||
            unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
 | 
			
		||||
            icon=ICON_LIGHTBULB,
 | 
			
		||||
            accuracy_decimals=2,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_VOLT_AMPS,
 | 
			
		||||
            unit_of_measurement=UNIT_WATT,
 | 
			
		||||
            accuracy_decimals=2,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
@@ -146,10 +137,6 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
 | 
			
		||||
        cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
 | 
			
		||||
        cv.Optional(CONF_OFFSET_VOLTAGE, default=0): cv.int_,
 | 
			
		||||
        cv.Optional(CONF_OFFSET_CURRENT, default=0): cv.int_,
 | 
			
		||||
        cv.Optional(CONF_OFFSET_ACTIVE_POWER, default=0): cv.int_,
 | 
			
		||||
        cv.Optional(CONF_OFFSET_REACTIVE_POWER, default=0): cv.int_,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -177,10 +164,9 @@ CONFIG_SCHEMA = (
 | 
			
		||||
            cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(
 | 
			
		||||
                CURRENT_PHASES, upper=True
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_GAIN_PGA, default="1X"): cv.enum(PGA_GAINS, upper=True),
 | 
			
		||||
            cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True),
 | 
			
		||||
            cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_ENABLE_OFFSET_CALIBRATION, default=False): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_ENABLE_GAIN_CALIBRATION, default=False): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
@@ -199,10 +185,6 @@ async def to_code(config):
 | 
			
		||||
        conf = config[phase]
 | 
			
		||||
        cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
 | 
			
		||||
        cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
 | 
			
		||||
        cg.add(var.set_voltage_offset(i, conf[CONF_OFFSET_VOLTAGE]))
 | 
			
		||||
        cg.add(var.set_current_offset(i, conf[CONF_OFFSET_CURRENT]))
 | 
			
		||||
        cg.add(var.set_active_power_offset(i, conf[CONF_OFFSET_ACTIVE_POWER]))
 | 
			
		||||
        cg.add(var.set_reactive_power_offset(i, conf[CONF_OFFSET_REACTIVE_POWER]))
 | 
			
		||||
        if voltage_config := conf.get(CONF_VOLTAGE):
 | 
			
		||||
            sens = await sensor.new_sensor(voltage_config)
 | 
			
		||||
            cg.add(var.set_voltage_sensor(i, sens))
 | 
			
		||||
@@ -236,15 +218,16 @@ async def to_code(config):
 | 
			
		||||
        if peak_current_config := conf.get(CONF_PEAK_CURRENT):
 | 
			
		||||
            sens = await sensor.new_sensor(peak_current_config)
 | 
			
		||||
            cg.add(var.set_peak_current_sensor(i, sens))
 | 
			
		||||
 | 
			
		||||
    if frequency_config := config.get(CONF_FREQUENCY):
 | 
			
		||||
        sens = await sensor.new_sensor(frequency_config)
 | 
			
		||||
        cg.add(var.set_freq_sensor(sens))
 | 
			
		||||
    if chip_temperature_config := config.get(CONF_CHIP_TEMPERATURE):
 | 
			
		||||
        sens = await sensor.new_sensor(chip_temperature_config)
 | 
			
		||||
        cg.add(var.set_chip_temperature_sensor(sens))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
 | 
			
		||||
    cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
 | 
			
		||||
    cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
 | 
			
		||||
    cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED]))
 | 
			
		||||
    cg.add(var.set_enable_offset_calibration(config[CONF_ENABLE_OFFSET_CALIBRATION]))
 | 
			
		||||
    cg.add(var.set_enable_gain_calibration(config[CONF_ENABLE_GAIN_CALIBRATION]))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import text_sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID, CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C
 | 
			
		||||
 | 
			
		||||
from ..sensor import ATM90E32Component
 | 
			
		||||
 | 
			
		||||
CONF_PHASE_STATUS = "phase_status"
 | 
			
		||||
CONF_FREQUENCY_STATUS = "frequency_status"
 | 
			
		||||
PHASE_KEYS = [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]
 | 
			
		||||
 | 
			
		||||
PHASE_STATUS_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_PHASE_A): text_sensor.text_sensor_schema(
 | 
			
		||||
            icon="mdi:flash-alert"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_PHASE_B): text_sensor.text_sensor_schema(
 | 
			
		||||
            icon="mdi:flash-alert"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_PHASE_C): text_sensor.text_sensor_schema(
 | 
			
		||||
            icon="mdi:flash-alert"
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(ATM90E32Component),
 | 
			
		||||
        cv.Optional(CONF_PHASE_STATUS): PHASE_STATUS_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_FREQUENCY_STATUS): text_sensor.text_sensor_schema(
 | 
			
		||||
            icon="mdi:lightbulb-alert"
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
 | 
			
		||||
    if phase_cfg := config.get(CONF_PHASE_STATUS):
 | 
			
		||||
        for i, key in enumerate(PHASE_KEYS):
 | 
			
		||||
            if sub_phase_cfg := phase_cfg.get(key):
 | 
			
		||||
                sens = await text_sensor.new_text_sensor(sub_phase_cfg)
 | 
			
		||||
                cg.add(parent.set_phase_status_text_sensor(i, sens))
 | 
			
		||||
 | 
			
		||||
    if freq_status_config := config.get(CONF_FREQUENCY_STATUS):
 | 
			
		||||
        sens = await text_sensor.new_text_sensor(freq_status_config)
 | 
			
		||||
        cg.add(parent.set_freq_status_text_sensor(sens))
 | 
			
		||||
@@ -37,32 +37,29 @@ AUDIO_COMPONENT_SCHEMA = cv.Schema(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_UNDEF = object()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def set_stream_limits(
 | 
			
		||||
    min_bits_per_sample: int = cv.UNDEFINED,
 | 
			
		||||
    max_bits_per_sample: int = cv.UNDEFINED,
 | 
			
		||||
    min_channels: int = cv.UNDEFINED,
 | 
			
		||||
    max_channels: int = cv.UNDEFINED,
 | 
			
		||||
    min_sample_rate: int = cv.UNDEFINED,
 | 
			
		||||
    max_sample_rate: int = cv.UNDEFINED,
 | 
			
		||||
    min_bits_per_sample: int = _UNDEF,
 | 
			
		||||
    max_bits_per_sample: int = _UNDEF,
 | 
			
		||||
    min_channels: int = _UNDEF,
 | 
			
		||||
    max_channels: int = _UNDEF,
 | 
			
		||||
    min_sample_rate: int = _UNDEF,
 | 
			
		||||
    max_sample_rate: int = _UNDEF,
 | 
			
		||||
):
 | 
			
		||||
    """Sets the limits for the audio stream that audio component can handle
 | 
			
		||||
 | 
			
		||||
    When the component sinks audio (e.g., a speaker), these indicate the limits to the audio it can receive.
 | 
			
		||||
    When the component sources audio (e.g., a microphone), these indicate the limits to the audio it can send.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def set_limits_in_config(config):
 | 
			
		||||
        if min_bits_per_sample is not cv.UNDEFINED:
 | 
			
		||||
        if min_bits_per_sample is not _UNDEF:
 | 
			
		||||
            config[CONF_MIN_BITS_PER_SAMPLE] = min_bits_per_sample
 | 
			
		||||
        if max_bits_per_sample is not cv.UNDEFINED:
 | 
			
		||||
        if max_bits_per_sample is not _UNDEF:
 | 
			
		||||
            config[CONF_MAX_BITS_PER_SAMPLE] = max_bits_per_sample
 | 
			
		||||
        if min_channels is not cv.UNDEFINED:
 | 
			
		||||
        if min_channels is not _UNDEF:
 | 
			
		||||
            config[CONF_MIN_CHANNELS] = min_channels
 | 
			
		||||
        if max_channels is not cv.UNDEFINED:
 | 
			
		||||
        if max_channels is not _UNDEF:
 | 
			
		||||
            config[CONF_MAX_CHANNELS] = max_channels
 | 
			
		||||
        if min_sample_rate is not cv.UNDEFINED:
 | 
			
		||||
        if min_sample_rate is not _UNDEF:
 | 
			
		||||
            config[CONF_MIN_SAMPLE_RATE] = min_sample_rate
 | 
			
		||||
        if max_sample_rate is not cv.UNDEFINED:
 | 
			
		||||
        if max_sample_rate is not _UNDEF:
 | 
			
		||||
            config[CONF_MAX_SAMPLE_RATE] = max_sample_rate
 | 
			
		||||
 | 
			
		||||
    return set_limits_in_config
 | 
			
		||||
@@ -72,87 +69,43 @@ def final_validate_audio_schema(
 | 
			
		||||
    name: str,
 | 
			
		||||
    *,
 | 
			
		||||
    audio_device: str,
 | 
			
		||||
    bits_per_sample: int = cv.UNDEFINED,
 | 
			
		||||
    channels: int = cv.UNDEFINED,
 | 
			
		||||
    sample_rate: int = cv.UNDEFINED,
 | 
			
		||||
    enabled_channels: list[int] = cv.UNDEFINED,
 | 
			
		||||
    audio_device_issue: bool = False,
 | 
			
		||||
    bits_per_sample: int,
 | 
			
		||||
    channels: int,
 | 
			
		||||
    sample_rate: int,
 | 
			
		||||
):
 | 
			
		||||
    """Validates audio compatibility when passed between different components.
 | 
			
		||||
 | 
			
		||||
    The component derived from ``AUDIO_COMPONENT_SCHEMA`` should call ``set_stream_limits`` in a validator to specify its compatible settings
 | 
			
		||||
 | 
			
		||||
      - If audio_device_issue is True, then the error message indicates the user should adjust the AUDIO_COMPONENT_SCHEMA derived component's configuration to match the values passed to this function
 | 
			
		||||
      - If audio_device_issue is False, then the error message indicates the user should adjust the configuration of the component calling this function, as it falls out of the valid stream limits
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        name (str): Friendly name of the component calling this function with an audio component to validate
 | 
			
		||||
        audio_device (str): The configuration parameter name that contains the ID of an AUDIO_COMPONENT_SCHEMA derived component to validate against
 | 
			
		||||
        bits_per_sample (int, optional): The desired bits per sample
 | 
			
		||||
        channels (int, optional): The desired number of channels
 | 
			
		||||
        sample_rate (int, optional): The desired sample rate
 | 
			
		||||
        enabled_channels (list[int], optional): The desired enabled channels
 | 
			
		||||
        audio_device_issue (bool, optional): Format the error message to indicate the problem is in the configuration for the ``audio_device`` component. Defaults to False.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def validate_audio_compatiblity(audio_config):
 | 
			
		||||
        audio_schema = {}
 | 
			
		||||
 | 
			
		||||
        if bits_per_sample is not cv.UNDEFINED:
 | 
			
		||||
        try:
 | 
			
		||||
            cv.int_range(
 | 
			
		||||
                min=audio_config.get(CONF_MIN_BITS_PER_SAMPLE),
 | 
			
		||||
                max=audio_config.get(CONF_MAX_BITS_PER_SAMPLE),
 | 
			
		||||
            )(bits_per_sample)
 | 
			
		||||
        except cv.Invalid as exc:
 | 
			
		||||
                if audio_device_issue:
 | 
			
		||||
                    error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires {bits_per_sample} bits per sample."
 | 
			
		||||
                else:
 | 
			
		||||
                    error_string = f"Invalid configuration for the {name} component. The {CONF_BITS_PER_SAMPLE} {str(exc)}"
 | 
			
		||||
                raise cv.Invalid(error_string) from exc
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"Invalid configuration for the {name} component. The {CONF_BITS_PER_SAMPLE} {str(exc)}"
 | 
			
		||||
            ) from exc
 | 
			
		||||
 | 
			
		||||
        if channels is not cv.UNDEFINED:
 | 
			
		||||
        try:
 | 
			
		||||
            cv.int_range(
 | 
			
		||||
                min=audio_config.get(CONF_MIN_CHANNELS),
 | 
			
		||||
                max=audio_config.get(CONF_MAX_CHANNELS),
 | 
			
		||||
            )(channels)
 | 
			
		||||
        except cv.Invalid as exc:
 | 
			
		||||
                if audio_device_issue:
 | 
			
		||||
                    error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires {channels} channels."
 | 
			
		||||
                else:
 | 
			
		||||
                    error_string = f"Invalid configuration for the {name} component. The {CONF_NUM_CHANNELS} {str(exc)}"
 | 
			
		||||
                raise cv.Invalid(error_string) from exc
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"Invalid configuration for the {name} component. The {CONF_NUM_CHANNELS} {str(exc)}"
 | 
			
		||||
            ) from exc
 | 
			
		||||
 | 
			
		||||
        if sample_rate is not cv.UNDEFINED:
 | 
			
		||||
        try:
 | 
			
		||||
            cv.int_range(
 | 
			
		||||
                min=audio_config.get(CONF_MIN_SAMPLE_RATE),
 | 
			
		||||
                max=audio_config.get(CONF_MAX_SAMPLE_RATE),
 | 
			
		||||
            )(sample_rate)
 | 
			
		||||
            except cv.Invalid as exc:
 | 
			
		||||
                if audio_device_issue:
 | 
			
		||||
                    error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires a {sample_rate} sample rate."
 | 
			
		||||
                else:
 | 
			
		||||
                    error_string = f"Invalid configuration for the {name} component. The {CONF_SAMPLE_RATE} {str(exc)}"
 | 
			
		||||
                raise cv.Invalid(error_string) from exc
 | 
			
		||||
 | 
			
		||||
        if enabled_channels is not cv.UNDEFINED:
 | 
			
		||||
            for channel in enabled_channels:
 | 
			
		||||
                try:
 | 
			
		||||
                    # Channels are 0-indexed
 | 
			
		||||
                    cv.int_range(
 | 
			
		||||
                        min=0,
 | 
			
		||||
                        max=audio_config.get(CONF_MAX_CHANNELS) - 1,
 | 
			
		||||
                    )(channel)
 | 
			
		||||
                except cv.Invalid as exc:
 | 
			
		||||
                    if audio_device_issue:
 | 
			
		||||
                        error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires channel {channel}."
 | 
			
		||||
                    else:
 | 
			
		||||
                        error_string = f"Invalid configuration for the {name} component. Enabled channel {channel} {str(exc)}"
 | 
			
		||||
                    raise cv.Invalid(error_string) from exc
 | 
			
		||||
 | 
			
		||||
            return cv.Schema(audio_schema, extra=cv.ALLOW_EXTRA)(audio_config)
 | 
			
		||||
        except cv.Invalid as exc:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"Invalid configuration for the {name} component. The {CONF_SAMPLE_RATE} {str(exc)}"
 | 
			
		||||
            ) from exc
 | 
			
		||||
 | 
			
		||||
    return cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
@@ -165,4 +118,4 @@ def final_validate_audio_schema(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add_library("esphome/esp-audio-libs", "1.1.4")
 | 
			
		||||
    cg.add_library("esphome/esp-audio-libs", "1.1.3")
 | 
			
		||||
 
 | 
			
		||||
@@ -135,53 +135,5 @@ const char *audio_file_type_to_string(AudioFileType file_type);
 | 
			
		||||
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
 | 
			
		||||
                         size_t samples_to_scale);
 | 
			
		||||
 | 
			
		||||
/// @brief Unpacks a quantized audio sample into a Q31 fixed-point number.
 | 
			
		||||
/// @param data Pointer to uint8_t array containing the audio sample
 | 
			
		||||
/// @param bytes_per_sample The number of bytes per sample
 | 
			
		||||
/// @return Q31 sample
 | 
			
		||||
inline int32_t unpack_audio_sample_to_q31(const uint8_t *data, size_t bytes_per_sample) {
 | 
			
		||||
  int32_t sample = 0;
 | 
			
		||||
  if (bytes_per_sample == 1) {
 | 
			
		||||
    sample |= data[0] << 24;
 | 
			
		||||
  } else if (bytes_per_sample == 2) {
 | 
			
		||||
    sample |= data[0] << 16;
 | 
			
		||||
    sample |= data[1] << 24;
 | 
			
		||||
  } else if (bytes_per_sample == 3) {
 | 
			
		||||
    sample |= data[0] << 8;
 | 
			
		||||
    sample |= data[1] << 16;
 | 
			
		||||
    sample |= data[2] << 24;
 | 
			
		||||
  } else if (bytes_per_sample == 4) {
 | 
			
		||||
    sample |= data[0];
 | 
			
		||||
    sample |= data[1] << 8;
 | 
			
		||||
    sample |= data[2] << 16;
 | 
			
		||||
    sample |= data[3] << 24;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return sample;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// @brief Packs a Q31 fixed-point number as an audio sample with the specified number of bytes per sample.
 | 
			
		||||
/// Packs the most significant bits - no dithering is applied.
 | 
			
		||||
/// @param sample Q31 fixed-point number to pack
 | 
			
		||||
/// @param data Pointer to data array to store
 | 
			
		||||
/// @param bytes_per_sample The audio data's bytes per sample
 | 
			
		||||
inline void pack_q31_as_audio_sample(int32_t sample, uint8_t *data, size_t bytes_per_sample) {
 | 
			
		||||
  if (bytes_per_sample == 1) {
 | 
			
		||||
    data[0] = static_cast<uint8_t>(sample >> 24);
 | 
			
		||||
  } else if (bytes_per_sample == 2) {
 | 
			
		||||
    data[0] = static_cast<uint8_t>(sample >> 16);
 | 
			
		||||
    data[1] = static_cast<uint8_t>(sample >> 24);
 | 
			
		||||
  } else if (bytes_per_sample == 3) {
 | 
			
		||||
    data[0] = static_cast<uint8_t>(sample >> 8);
 | 
			
		||||
    data[1] = static_cast<uint8_t>(sample >> 16);
 | 
			
		||||
    data[2] = static_cast<uint8_t>(sample >> 24);
 | 
			
		||||
  } else if (bytes_per_sample == 4) {
 | 
			
		||||
    data[0] = static_cast<uint8_t>(sample);
 | 
			
		||||
    data[1] = static_cast<uint8_t>(sample >> 8);
 | 
			
		||||
    data[2] = static_cast<uint8_t>(sample >> 16);
 | 
			
		||||
    data[3] = static_cast<uint8_t>(sample >> 24);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace audio
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -171,7 +171,7 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
 | 
			
		||||
 | 
			
		||||
    bytes_available_before_processing = this->input_transfer_buffer_->available();
 | 
			
		||||
 | 
			
		||||
    if ((this->potentially_failed_count_ > 0) && (bytes_read == 0)) {
 | 
			
		||||
    if ((this->potentially_failed_count_ > 10) && (bytes_read == 0)) {
 | 
			
		||||
      // Failed to decode in last attempt and there is no new data
 | 
			
		||||
 | 
			
		||||
      if ((this->input_transfer_buffer_->free() == 0) && first_loop_iteration) {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace audio {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,6 @@
 | 
			
		||||
#include "audio_transfer_buffer.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/ring_buffer.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SPEAKER
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import climate_ir
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["climate_ir"]
 | 
			
		||||
CODEOWNERS = ["@bazuchan"]
 | 
			
		||||
@@ -7,8 +9,13 @@ 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(BalluClimate)
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(BalluClimate),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    await climate_ir.new_climate_ir(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await climate_ir.register_climate_ir(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_DEFAULT_TARGET_TEMPERATURE_LOW,
 | 
			
		||||
    CONF_HEAT_ACTION,
 | 
			
		||||
    CONF_HUMIDITY_SENSOR,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_IDLE_ACTION,
 | 
			
		||||
    CONF_SENSOR,
 | 
			
		||||
)
 | 
			
		||||
@@ -18,9 +19,9 @@ BangBangClimate = bang_bang_ns.class_("BangBangClimate", climate.Climate, cg.Com
 | 
			
		||||
BangBangClimateTargetTempConfig = bang_bang_ns.struct("BangBangClimateTargetTempConfig")
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    climate.climate_schema(BangBangClimate)
 | 
			
		||||
    .extend(
 | 
			
		||||
    climate.CLIMATE_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BangBangClimate),
 | 
			
		||||
            cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
 | 
			
		||||
            cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor),
 | 
			
		||||
            cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
 | 
			
		||||
@@ -35,15 +36,15 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await climate.new_climate(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await climate.register_climate(var, config)
 | 
			
		||||
 | 
			
		||||
    sens = await cg.get_variable(config[CONF_SENSOR])
 | 
			
		||||
    cg.add(var.set_sensor(sens))
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@
 | 
			
		||||
#include "bedjet_hub.h"
 | 
			
		||||
#include "bedjet_child.h"
 | 
			
		||||
#include "bedjet_const.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,11 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import ble_client, climate
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_HEAT_MODE,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_RECEIVE_TIMEOUT,
 | 
			
		||||
    CONF_TEMPERATURE_SOURCE,
 | 
			
		||||
    CONF_TIME_ID,
 | 
			
		||||
@@ -10,6 +13,7 @@ from esphome.const import (
 | 
			
		||||
 | 
			
		||||
from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
CODEOWNERS = ["@jhansche"]
 | 
			
		||||
DEPENDENCIES = ["bedjet"]
 | 
			
		||||
 | 
			
		||||
@@ -26,9 +30,9 @@ BEDJET_TEMPERATURE_SOURCES = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    climate.climate_schema(BedJetClimate)
 | 
			
		||||
    .extend(
 | 
			
		||||
    climate.CLIMATE_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BedJetClimate),
 | 
			
		||||
            cv.Optional(CONF_HEAT_MODE, default="heat"): cv.enum(
 | 
			
		||||
                BEDJET_HEAT_MODES, lower=True
 | 
			
		||||
            ),
 | 
			
		||||
@@ -59,8 +63,9 @@ CONFIG_SCHEMA = (
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await climate.new_climate(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await climate.register_climate(var, config)
 | 
			
		||||
    await register_bedjet_child(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_heating_mode(config[CONF_HEAT_MODE]))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,31 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import fan
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
CODEOWNERS = ["@jhansche"]
 | 
			
		||||
DEPENDENCIES = ["bedjet"]
 | 
			
		||||
 | 
			
		||||
BedJetFan = bedjet_ns.class_("BedJetFan", fan.Fan, cg.PollingComponent)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    fan.fan_schema(BedJetFan)
 | 
			
		||||
    fan.FAN_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BedJetFan),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(BEDJET_CLIENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await fan.new_fan(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await fan.register_fan(var, config)
 | 
			
		||||
    await register_bedjet_child(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,31 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import fan, output
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, CONF_OUTPUT
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DIRECTION_OUTPUT,
 | 
			
		||||
    CONF_OSCILLATION_OUTPUT,
 | 
			
		||||
    CONF_OUTPUT,
 | 
			
		||||
    CONF_OUTPUT_ID,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from .. import binary_ns
 | 
			
		||||
 | 
			
		||||
BinaryFan = binary_ns.class_("BinaryFan", fan.Fan, cg.Component)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    fan.fan_schema(BinaryFan)
 | 
			
		||||
    .extend(
 | 
			
		||||
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan),
 | 
			
		||||
        cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
 | 
			
		||||
        cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
 | 
			
		||||
        cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
 | 
			
		||||
    }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await fan.new_fan(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await fan.register_fan(var, config)
 | 
			
		||||
 | 
			
		||||
    output_ = await cg.get_variable(config[CONF_OUTPUT])
 | 
			
		||||
    cg.add(var.set_output(output_))
 | 
			
		||||
 
 | 
			
		||||
@@ -386,7 +386,7 @@ def validate_click_timing(value):
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_BINARY_SENSOR_SCHEMA = (
 | 
			
		||||
BINARY_SENSOR_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
@@ -458,17 +458,19 @@ _BINARY_SENSOR_SCHEMA = (
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_UNDEF = object()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def binary_sensor_schema(
 | 
			
		||||
    class_: MockObjClass = cv.UNDEFINED,
 | 
			
		||||
    class_: MockObjClass = _UNDEF,
 | 
			
		||||
    *,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
    device_class: str = cv.UNDEFINED,
 | 
			
		||||
    icon: str = _UNDEF,
 | 
			
		||||
    entity_category: str = _UNDEF,
 | 
			
		||||
    device_class: str = _UNDEF,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {}
 | 
			
		||||
 | 
			
		||||
    if class_ is not cv.UNDEFINED:
 | 
			
		||||
    if class_ is not _UNDEF:
 | 
			
		||||
        # Not cv.optional
 | 
			
		||||
        schema[cv.GenerateID()] = cv.declare_id(class_)
 | 
			
		||||
 | 
			
		||||
@@ -477,15 +479,10 @@ def binary_sensor_schema(
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_DEVICE_CLASS, device_class, validate_device_class),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
        if default is not _UNDEF:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return _BINARY_SENSOR_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
BINARY_SENSOR_SCHEMA = binary_sensor_schema()
 | 
			
		||||
BINARY_SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("binary_sensor"))
 | 
			
		||||
    return BINARY_SENSOR_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_binary_sensor_core_(var, config):
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ static const uint8_t BL0906_WRITE_COMMAND = 0xCA;
 | 
			
		||||
static const uint8_t BL0906_V_RMS = 0x16;
 | 
			
		||||
 | 
			
		||||
// Total power
 | 
			
		||||
static const uint8_t BL0906_WATT_SUM = 0x2C;
 | 
			
		||||
static const uint8_t BL0906_WATT_SUM = 0X2C;
 | 
			
		||||
 | 
			
		||||
// Current1~6
 | 
			
		||||
static const uint8_t BL0906_I_1_RMS = 0x0D;  // current_1
 | 
			
		||||
@@ -56,29 +56,29 @@ static const uint8_t BL0906_I_5_RMS = 0x13;
 | 
			
		||||
static const uint8_t BL0906_I_6_RMS = 0x14;  // current_6
 | 
			
		||||
 | 
			
		||||
// Power1~6
 | 
			
		||||
static const uint8_t BL0906_WATT_1 = 0x23;  // power_1
 | 
			
		||||
static const uint8_t BL0906_WATT_2 = 0x24;
 | 
			
		||||
static const uint8_t BL0906_WATT_3 = 0x25;
 | 
			
		||||
static const uint8_t BL0906_WATT_4 = 0x26;
 | 
			
		||||
static const uint8_t BL0906_WATT_5 = 0x29;
 | 
			
		||||
static const uint8_t BL0906_WATT_6 = 0x2A;  // power_6
 | 
			
		||||
static const uint8_t BL0906_WATT_1 = 0X23;  // power_1
 | 
			
		||||
static const uint8_t BL0906_WATT_2 = 0X24;
 | 
			
		||||
static const uint8_t BL0906_WATT_3 = 0X25;
 | 
			
		||||
static const uint8_t BL0906_WATT_4 = 0X26;
 | 
			
		||||
static const uint8_t BL0906_WATT_5 = 0X29;
 | 
			
		||||
static const uint8_t BL0906_WATT_6 = 0X2A;  // power_6
 | 
			
		||||
 | 
			
		||||
// Active pulse count, unsigned
 | 
			
		||||
static const uint8_t BL0906_CF_1_CNT = 0x30;  // Channel_1
 | 
			
		||||
static const uint8_t BL0906_CF_2_CNT = 0x31;
 | 
			
		||||
static const uint8_t BL0906_CF_3_CNT = 0x32;
 | 
			
		||||
static const uint8_t BL0906_CF_4_CNT = 0x33;
 | 
			
		||||
static const uint8_t BL0906_CF_5_CNT = 0x36;
 | 
			
		||||
static const uint8_t BL0906_CF_6_CNT = 0x37;  // Channel_6
 | 
			
		||||
static const uint8_t BL0906_CF_1_CNT = 0X30;  // Channel_1
 | 
			
		||||
static const uint8_t BL0906_CF_2_CNT = 0X31;
 | 
			
		||||
static const uint8_t BL0906_CF_3_CNT = 0X32;
 | 
			
		||||
static const uint8_t BL0906_CF_4_CNT = 0X33;
 | 
			
		||||
static const uint8_t BL0906_CF_5_CNT = 0X36;
 | 
			
		||||
static const uint8_t BL0906_CF_6_CNT = 0X37;  // Channel_6
 | 
			
		||||
 | 
			
		||||
// Total active pulse count, unsigned
 | 
			
		||||
static const uint8_t BL0906_CF_SUM_CNT = 0x39;
 | 
			
		||||
static const uint8_t BL0906_CF_SUM_CNT = 0X39;
 | 
			
		||||
 | 
			
		||||
// Voltage frequency cycle
 | 
			
		||||
static const uint8_t BL0906_FREQUENCY = 0x4E;
 | 
			
		||||
static const uint8_t BL0906_FREQUENCY = 0X4E;
 | 
			
		||||
 | 
			
		||||
// Internal temperature
 | 
			
		||||
static const uint8_t BL0906_TEMPERATURE = 0x5E;
 | 
			
		||||
static const uint8_t BL0906_TEMPERATURE = 0X5E;
 | 
			
		||||
 | 
			
		||||
// Calibration register
 | 
			
		||||
// RMS gain adjustment register
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ from esphome.components import ble_client, esp32_ble_tracker, text_sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_CHARACTERISTIC_UUID,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_NOTIFY,
 | 
			
		||||
    CONF_SERVICE_UUID,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
@@ -31,9 +32,9 @@ BLETextSensorNotifyTrigger = ble_client_ns.class_(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    text_sensor.text_sensor_schema(BLETextSensor)
 | 
			
		||||
    .extend(
 | 
			
		||||
    text_sensor.TEXT_SENSOR_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BLETextSensor),
 | 
			
		||||
            cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
            cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
            cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
@@ -53,7 +54,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await text_sensor.new_text_sensor(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
 | 
			
		||||
@@ -100,6 +101,7 @@ async def to_code(config):
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
    cg.add(var.set_enable_notify(config[CONF_NOTIFY]))
 | 
			
		||||
    await text_sensor.register_text_sensor(var, config)
 | 
			
		||||
    for conf in config.get(CONF_ON_NOTIFY, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await ble_client.register_ble_node(trigger, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -73,8 +73,9 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->read.handle;
 | 
			
		||||
      resp.data.reserve(param->read.value_len);
 | 
			
		||||
      // Use bulk insert instead of individual push_backs
 | 
			
		||||
      resp.data.insert(resp.data.end(), param->read.value, param->read.value + param->read.value_len);
 | 
			
		||||
      for (uint16_t i = 0; i < param->read.value_len; i++) {
 | 
			
		||||
        resp.data.push_back(param->read.value[i]);
 | 
			
		||||
      }
 | 
			
		||||
      this->proxy_->get_api_connection()->send_bluetooth_gatt_read_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -126,8 +127,9 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->notify.handle;
 | 
			
		||||
      resp.data.reserve(param->notify.value_len);
 | 
			
		||||
      // Use bulk insert instead of individual push_backs
 | 
			
		||||
      resp.data.insert(resp.data.end(), param->notify.value, param->notify.value + param->notify.value_len);
 | 
			
		||||
      for (uint16_t i = 0; i < param->notify.value_len; i++) {
 | 
			
		||||
        resp.data.push_back(param->notify.value[i]);
 | 
			
		||||
      }
 | 
			
		||||
      this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_data_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/macros.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
@@ -26,22 +25,6 @@ std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
 | 
			
		||||
 | 
			
		||||
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::setup() {
 | 
			
		||||
  this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
 | 
			
		||||
    if (this->api_connection_ != nullptr) {
 | 
			
		||||
      this->send_bluetooth_scanner_state_(state);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state) {
 | 
			
		||||
  api::BluetoothScannerStateResponse resp;
 | 
			
		||||
  resp.state = static_cast<api::enums::BluetoothScannerState>(state);
 | 
			
		||||
  resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
 | 
			
		||||
                                               : api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
 | 
			
		||||
  this->api_connection_->send_bluetooth_scanner_state_response(resp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
			
		||||
  if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_)
 | 
			
		||||
    return false;
 | 
			
		||||
@@ -52,60 +35,33 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static constexpr size_t FLUSH_BATCH_SIZE = 8;
 | 
			
		||||
static std::vector<api::BluetoothLERawAdvertisement> &get_batch_buffer() {
 | 
			
		||||
  static std::vector<api::BluetoothLERawAdvertisement> batch_buffer;
 | 
			
		||||
  return batch_buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) {
 | 
			
		||||
  if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  // Get the batch buffer reference
 | 
			
		||||
  auto &batch_buffer = get_batch_buffer();
 | 
			
		||||
 | 
			
		||||
  // Reserve additional capacity if needed
 | 
			
		||||
  size_t new_size = batch_buffer.size() + count;
 | 
			
		||||
  if (batch_buffer.capacity() < new_size) {
 | 
			
		||||
    batch_buffer.reserve(new_size);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Add new advertisements to the batch buffer
 | 
			
		||||
  api::BluetoothLERawAdvertisementsResponse resp;
 | 
			
		||||
  for (size_t i = 0; i < count; i++) {
 | 
			
		||||
    auto &result = advertisements[i];
 | 
			
		||||
    uint8_t length = result.adv_data_len + result.scan_rsp_len;
 | 
			
		||||
 | 
			
		||||
    batch_buffer.emplace_back();
 | 
			
		||||
    auto &adv = batch_buffer.back();
 | 
			
		||||
    api::BluetoothLERawAdvertisement adv;
 | 
			
		||||
    adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
 | 
			
		||||
    adv.rssi = result.rssi;
 | 
			
		||||
    adv.address_type = result.ble_addr_type;
 | 
			
		||||
    adv.data.assign(&result.ble_adv[0], &result.ble_adv[length]);
 | 
			
		||||
 | 
			
		||||
    ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
 | 
			
		||||
    uint8_t length = result.adv_data_len + result.scan_rsp_len;
 | 
			
		||||
    adv.data.reserve(length);
 | 
			
		||||
    for (uint16_t i = 0; i < length; i++) {
 | 
			
		||||
      adv.data.push_back(result.ble_adv[i]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    resp.advertisements.push_back(std::move(adv));
 | 
			
		||||
 | 
			
		||||
    ESP_LOGV(TAG, "Proxying raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
 | 
			
		||||
             result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Only send if we've accumulated a good batch size to maximize batching efficiency
 | 
			
		||||
  // https://github.com/esphome/backlog/issues/21
 | 
			
		||||
  if (batch_buffer.size() >= FLUSH_BATCH_SIZE) {
 | 
			
		||||
    this->flush_pending_advertisements();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Proxying %d packets", count);
 | 
			
		||||
  this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::flush_pending_advertisements() {
 | 
			
		||||
  auto &batch_buffer = get_batch_buffer();
 | 
			
		||||
  if (batch_buffer.empty() || !api::global_api_server->is_connected() || this->api_connection_ == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  api::BluetoothLERawAdvertisementsResponse resp;
 | 
			
		||||
  resp.advertisements.swap(batch_buffer);
 | 
			
		||||
  this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
			
		||||
  api::BluetoothLEAdvertisementResponse resp;
 | 
			
		||||
  resp.address = device.address_uint64();
 | 
			
		||||
@@ -113,34 +69,21 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
 | 
			
		||||
  if (!device.get_name().empty())
 | 
			
		||||
    resp.name = device.get_name();
 | 
			
		||||
  resp.rssi = device.get_rssi();
 | 
			
		||||
 | 
			
		||||
  // Pre-allocate vectors based on known sizes
 | 
			
		||||
  auto service_uuids = device.get_service_uuids();
 | 
			
		||||
  resp.service_uuids.reserve(service_uuids.size());
 | 
			
		||||
  for (auto &uuid : service_uuids) {
 | 
			
		||||
    resp.service_uuids.emplace_back(uuid.to_string());
 | 
			
		||||
  for (auto uuid : device.get_service_uuids()) {
 | 
			
		||||
    resp.service_uuids.push_back(uuid.to_string());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Pre-allocate service data vector
 | 
			
		||||
  auto service_datas = device.get_service_datas();
 | 
			
		||||
  resp.service_data.reserve(service_datas.size());
 | 
			
		||||
  for (auto &data : service_datas) {
 | 
			
		||||
    resp.service_data.emplace_back();
 | 
			
		||||
    auto &service_data = resp.service_data.back();
 | 
			
		||||
  for (auto &data : device.get_service_datas()) {
 | 
			
		||||
    api::BluetoothServiceData service_data;
 | 
			
		||||
    service_data.uuid = data.uuid.to_string();
 | 
			
		||||
    service_data.data.assign(data.data.begin(), data.data.end());
 | 
			
		||||
    resp.service_data.push_back(std::move(service_data));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Pre-allocate manufacturer data vector
 | 
			
		||||
  auto manufacturer_datas = device.get_manufacturer_datas();
 | 
			
		||||
  resp.manufacturer_data.reserve(manufacturer_datas.size());
 | 
			
		||||
  for (auto &data : manufacturer_datas) {
 | 
			
		||||
    resp.manufacturer_data.emplace_back();
 | 
			
		||||
    auto &manufacturer_data = resp.manufacturer_data.back();
 | 
			
		||||
  for (auto &data : device.get_manufacturer_datas()) {
 | 
			
		||||
    api::BluetoothServiceData manufacturer_data;
 | 
			
		||||
    manufacturer_data.uuid = data.uuid.to_string();
 | 
			
		||||
    manufacturer_data.data.assign(data.data.begin(), data.data.end());
 | 
			
		||||
    resp.manufacturer_data.push_back(std::move(manufacturer_data));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->api_connection_->send_bluetooth_le_advertisement(resp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -174,18 +117,6 @@ void BluetoothProxy::loop() {
 | 
			
		||||
    }
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Flush any pending BLE advertisements that have been accumulated but not yet sent
 | 
			
		||||
  if (this->raw_advertisements_) {
 | 
			
		||||
    static uint32_t last_flush_time = 0;
 | 
			
		||||
    uint32_t now = App.get_loop_component_start_time();
 | 
			
		||||
 | 
			
		||||
    // Flush accumulated advertisements every 100ms
 | 
			
		||||
    if (now - last_flush_time >= 100) {
 | 
			
		||||
      this->flush_pending_advertisements();
 | 
			
		||||
      last_flush_time = now;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  for (auto *connection : this->connections_) {
 | 
			
		||||
    if (connection->send_service_ == connection->service_count_) {
 | 
			
		||||
      connection->send_service_ = DONE_SENDING_SERVICES;
 | 
			
		||||
@@ -214,27 +145,11 @@ void BluetoothProxy::loop() {
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTGetServicesResponse resp;
 | 
			
		||||
      resp.address = connection->get_address();
 | 
			
		||||
      resp.services.reserve(1);  // Always one service per response in this implementation
 | 
			
		||||
      api::BluetoothGATTService service_resp;
 | 
			
		||||
      service_resp.uuid = get_128bit_uuid_vec(service_result.uuid);
 | 
			
		||||
      service_resp.handle = service_result.start_handle;
 | 
			
		||||
      uint16_t char_offset = 0;
 | 
			
		||||
      esp_gattc_char_elem_t char_result;
 | 
			
		||||
      // Get the number of characteristics directly with one call
 | 
			
		||||
      uint16_t total_char_count = 0;
 | 
			
		||||
      esp_gatt_status_t char_count_status = esp_ble_gattc_get_attr_count(
 | 
			
		||||
          connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_CHARACTERISTIC,
 | 
			
		||||
          service_result.start_handle, service_result.end_handle, 0, &total_char_count);
 | 
			
		||||
 | 
			
		||||
      if (char_count_status == ESP_GATT_OK && total_char_count > 0) {
 | 
			
		||||
        // Only reserve if we successfully got a count
 | 
			
		||||
        service_resp.characteristics.reserve(total_char_count);
 | 
			
		||||
      } else if (char_count_status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", connection->get_connection_index(),
 | 
			
		||||
                 connection->address_str().c_str(), char_count_status);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Now process characteristics
 | 
			
		||||
      while (true) {  // characteristics
 | 
			
		||||
        uint16_t char_count = 1;
 | 
			
		||||
        esp_gatt_status_t char_status = esp_ble_gattc_get_all_char(
 | 
			
		||||
@@ -256,23 +171,6 @@ void BluetoothProxy::loop() {
 | 
			
		||||
        characteristic_resp.handle = char_result.char_handle;
 | 
			
		||||
        characteristic_resp.properties = char_result.properties;
 | 
			
		||||
        char_offset++;
 | 
			
		||||
 | 
			
		||||
        // Get the number of descriptors directly with one call
 | 
			
		||||
        uint16_t total_desc_count = 0;
 | 
			
		||||
        esp_gatt_status_t desc_count_status =
 | 
			
		||||
            esp_ble_gattc_get_attr_count(connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_DESCRIPTOR,
 | 
			
		||||
                                         char_result.char_handle, service_result.end_handle, 0, &total_desc_count);
 | 
			
		||||
 | 
			
		||||
        if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) {
 | 
			
		||||
          // Only reserve if we successfully got a count
 | 
			
		||||
          characteristic_resp.descriptors.reserve(total_desc_count);
 | 
			
		||||
        } else if (desc_count_status != ESP_GATT_OK) {
 | 
			
		||||
          ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d",
 | 
			
		||||
                   connection->get_connection_index(), connection->address_str().c_str(), char_result.char_handle,
 | 
			
		||||
                   desc_count_status);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Now process descriptors
 | 
			
		||||
        uint16_t desc_offset = 0;
 | 
			
		||||
        esp_gattc_descr_elem_t desc_result;
 | 
			
		||||
        while (true) {  // descriptors
 | 
			
		||||
@@ -555,8 +453,6 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
 | 
			
		||||
  this->api_connection_ = api_connection;
 | 
			
		||||
  this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
 | 
			
		||||
  this->parent_->recalculate_advertisement_parser_types();
 | 
			
		||||
 | 
			
		||||
  this->send_bluetooth_scanner_state_(this->parent_->get_scanner_state());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) {
 | 
			
		||||
@@ -629,17 +525,6 @@ void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_e
 | 
			
		||||
  this->api_connection_->send_bluetooth_device_unpairing_response(call);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
 | 
			
		||||
  if (this->parent_->get_scan_active() == active) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Setting scanner mode to %s", active ? "active" : "passive");
 | 
			
		||||
  this->parent_->set_scan_active(active);
 | 
			
		||||
  this->parent_->stop_scan();
 | 
			
		||||
  this->parent_->set_scan_continuous(
 | 
			
		||||
      true);  // Set this to true to automatically start scanning again when it has cleaned up.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BluetoothProxy *global_bluetooth_proxy = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
}  // namespace bluetooth_proxy
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,6 @@ enum BluetoothProxyFeature : uint32_t {
 | 
			
		||||
  FEATURE_PAIRING = 1 << 3,
 | 
			
		||||
  FEATURE_CACHE_CLEARING = 1 << 4,
 | 
			
		||||
  FEATURE_RAW_ADVERTISEMENTS = 1 << 5,
 | 
			
		||||
  FEATURE_STATE_AND_MODE = 1 << 6,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum BluetoothProxySubscriptionFlag : uint32_t {
 | 
			
		||||
@@ -54,9 +53,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
			
		||||
  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
 | 
			
		||||
  bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void flush_pending_advertisements();
 | 
			
		||||
  esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
 | 
			
		||||
 | 
			
		||||
  void register_connection(BluetoothConnection *connection) {
 | 
			
		||||
@@ -87,8 +84,6 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
			
		||||
  void send_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK);
 | 
			
		||||
 | 
			
		||||
  void bluetooth_scanner_set_mode(bool active);
 | 
			
		||||
 | 
			
		||||
  static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) {
 | 
			
		||||
    bd_addr[0] = (address >> 40) & 0xff;
 | 
			
		||||
    bd_addr[1] = (address >> 32) & 0xff;
 | 
			
		||||
@@ -112,7 +107,6 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
			
		||||
    uint32_t flags = 0;
 | 
			
		||||
    flags |= BluetoothProxyFeature::FEATURE_PASSIVE_SCAN;
 | 
			
		||||
    flags |= BluetoothProxyFeature::FEATURE_RAW_ADVERTISEMENTS;
 | 
			
		||||
    flags |= BluetoothProxyFeature::FEATURE_STATE_AND_MODE;
 | 
			
		||||
    if (this->active_) {
 | 
			
		||||
      flags |= BluetoothProxyFeature::FEATURE_ACTIVE_CONNECTIONS;
 | 
			
		||||
      flags |= BluetoothProxyFeature::FEATURE_REMOTE_CACHING;
 | 
			
		||||
@@ -130,7 +124,6 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
 | 
			
		||||
  void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
 | 
			
		||||
 | 
			
		||||
  BluetoothConnection *get_connection_(uint64_t address, bool reserve);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ ButtonPressTrigger = button_ns.class_(
 | 
			
		||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_BUTTON_SCHEMA = (
 | 
			
		||||
BUTTON_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
@@ -60,13 +60,15 @@ _BUTTON_SCHEMA = (
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_UNDEF = object()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def button_schema(
 | 
			
		||||
    class_: MockObjClass,
 | 
			
		||||
    *,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
    device_class: str = cv.UNDEFINED,
 | 
			
		||||
    icon: str = _UNDEF,
 | 
			
		||||
    entity_category: str = _UNDEF,
 | 
			
		||||
    device_class: str = _UNDEF,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {cv.GenerateID(): cv.declare_id(class_)}
 | 
			
		||||
 | 
			
		||||
@@ -75,15 +77,10 @@ def button_schema(
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_DEVICE_CLASS, device_class, validate_device_class),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
        if default is not _UNDEF:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return _BUTTON_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
BUTTON_SCHEMA = button_schema(Button)
 | 
			
		||||
BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button"))
 | 
			
		||||
    return BUTTON_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_button_core_(var, config):
 | 
			
		||||
 
 | 
			
		||||
@@ -86,9 +86,6 @@ void Canbus::loop() {
 | 
			
		||||
      data.push_back(can_message.data[i]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->callback_manager_(can_message.can_id, can_message.use_extended_id, can_message.remote_transmission_request,
 | 
			
		||||
                            data);
 | 
			
		||||
 | 
			
		||||
    // fire all triggers
 | 
			
		||||
    for (auto *trigger : this->triggers_) {
 | 
			
		||||
      if ((trigger->can_id_ == (can_message.can_id & trigger->can_id_mask_)) &&
 | 
			
		||||
 
 | 
			
		||||
@@ -81,20 +81,6 @@ class Canbus : public Component {
 | 
			
		||||
  void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; }
 | 
			
		||||
 | 
			
		||||
  void add_trigger(CanbusTrigger *trigger);
 | 
			
		||||
  /**
 | 
			
		||||
   * Add a callback to be called when a CAN message is received. All received messages
 | 
			
		||||
   * are passed to the callback without filtering.
 | 
			
		||||
   *
 | 
			
		||||
   * The callback function receives:
 | 
			
		||||
   * - can_id of the received data
 | 
			
		||||
   * - extended_id True if the can_id is an extended id
 | 
			
		||||
   * - rtr If this is a remote transmission request
 | 
			
		||||
   * - data The message data
 | 
			
		||||
   */
 | 
			
		||||
  void add_callback(
 | 
			
		||||
      std::function<void(uint32_t can_id, bool extended_id, bool rtr, const std::vector<uint8_t> &data)> callback) {
 | 
			
		||||
    this->callback_manager_.add(std::move(callback));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  template<typename... Ts> friend class CanbusSendAction;
 | 
			
		||||
@@ -102,8 +88,6 @@ class Canbus : public Component {
 | 
			
		||||
  uint32_t can_id_;
 | 
			
		||||
  bool use_extended_id_;
 | 
			
		||||
  CanSpeed bit_rate_;
 | 
			
		||||
  CallbackManager<void(uint32_t can_id, bool extended_id, bool rtr, const std::vector<uint8_t> &data)>
 | 
			
		||||
      callback_manager_{};
 | 
			
		||||
 | 
			
		||||
  virtual bool setup_internal();
 | 
			
		||||
  virtual Error send_message(struct CanFrame *frame);
 | 
			
		||||
 
 | 
			
		||||
@@ -32,14 +32,14 @@ CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(CCS811Component),
 | 
			
		||||
            cv.Optional(CONF_ECO2): sensor.sensor_schema(
 | 
			
		||||
            cv.Required(CONF_ECO2): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_PARTS_PER_MILLION,
 | 
			
		||||
                icon=ICON_MOLECULE_CO2,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                device_class=DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TVOC): sensor.sensor_schema(
 | 
			
		||||
            cv.Required(CONF_TVOC): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_PARTS_PER_BILLION,
 | 
			
		||||
                icon=ICON_RADIATOR,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
@@ -64,12 +64,9 @@ async def to_code(config):
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
 | 
			
		||||
    if eco2_config := config.get(CONF_ECO2):
 | 
			
		||||
        sens = await sensor.new_sensor(eco2_config)
 | 
			
		||||
    sens = await sensor.new_sensor(config[CONF_ECO2])
 | 
			
		||||
    cg.add(var.set_co2(sens))
 | 
			
		||||
 | 
			
		||||
    if tvoc_config := config.get(CONF_TVOC):
 | 
			
		||||
        sens = await sensor.new_sensor(tvoc_config)
 | 
			
		||||
    sens = await sensor.new_sensor(config[CONF_TVOC])
 | 
			
		||||
    cg.add(var.set_tvoc(sens))
 | 
			
		||||
 | 
			
		||||
    if version_config := config.get(CONF_VERSION):
 | 
			
		||||
 
 | 
			
		||||
@@ -11,11 +11,9 @@ from esphome.const import (
 | 
			
		||||
    CONF_CURRENT_TEMPERATURE_STATE_TOPIC,
 | 
			
		||||
    CONF_CUSTOM_FAN_MODE,
 | 
			
		||||
    CONF_CUSTOM_PRESET,
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
    CONF_FAN_MODE,
 | 
			
		||||
    CONF_FAN_MODE_COMMAND_TOPIC,
 | 
			
		||||
    CONF_FAN_MODE_STATE_TOPIC,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MAX_TEMPERATURE,
 | 
			
		||||
    CONF_MIN_TEMPERATURE,
 | 
			
		||||
@@ -48,7 +46,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_WEB_SERVER,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.cpp_generator import MockObjClass
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
@@ -154,11 +151,12 @@ ControlTrigger = climate_ns.class_(
 | 
			
		||||
    "ControlTrigger", automation.Trigger.template(ClimateCall.operator("ref"))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_CLIMATE_SCHEMA = (
 | 
			
		||||
CLIMATE_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Climate),
 | 
			
		||||
            cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
 | 
			
		||||
            cv.Optional(CONF_VISUAL, default={}): cv.Schema(
 | 
			
		||||
                {
 | 
			
		||||
@@ -247,31 +245,6 @@ _CLIMATE_SCHEMA = (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def climate_schema(
 | 
			
		||||
    class_: MockObjClass,
 | 
			
		||||
    *,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(class_),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for key, default, validator in [
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_ICON, icon, cv.icon),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return _CLIMATE_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
CLIMATE_SCHEMA = climate_schema(Climate)
 | 
			
		||||
CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_climate_core_(var, config):
 | 
			
		||||
    await setup_entity(var, config)
 | 
			
		||||
 | 
			
		||||
@@ -446,12 +419,6 @@ async def register_climate(var, config):
 | 
			
		||||
    await setup_climate_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def new_climate(config, *args):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], *args)
 | 
			
		||||
    await register_climate(var, config)
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(Climate),
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ enum ClimateMode : uint8_t {
 | 
			
		||||
  CLIMATE_MODE_FAN_ONLY = 4,
 | 
			
		||||
  /// The climate device is set to dry/humidity mode
 | 
			
		||||
  CLIMATE_MODE_DRY = 5,
 | 
			
		||||
  /** The climate device is adjusting the temperature dynamically.
 | 
			
		||||
  /** 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.
 | 
			
		||||
   */
 | 
			
		||||
 
 | 
			
		||||
@@ -40,24 +40,24 @@ namespace climate {
 | 
			
		||||
 */
 | 
			
		||||
class ClimateTraits {
 | 
			
		||||
 public:
 | 
			
		||||
  bool get_supports_current_temperature() const { return this->supports_current_temperature_; }
 | 
			
		||||
  bool get_supports_current_temperature() const { return supports_current_temperature_; }
 | 
			
		||||
  void set_supports_current_temperature(bool supports_current_temperature) {
 | 
			
		||||
    this->supports_current_temperature_ = supports_current_temperature;
 | 
			
		||||
    supports_current_temperature_ = supports_current_temperature;
 | 
			
		||||
  }
 | 
			
		||||
  bool get_supports_current_humidity() const { return this->supports_current_humidity_; }
 | 
			
		||||
  bool get_supports_current_humidity() const { return supports_current_humidity_; }
 | 
			
		||||
  void set_supports_current_humidity(bool supports_current_humidity) {
 | 
			
		||||
    this->supports_current_humidity_ = supports_current_humidity;
 | 
			
		||||
    supports_current_humidity_ = supports_current_humidity;
 | 
			
		||||
  }
 | 
			
		||||
  bool get_supports_two_point_target_temperature() const { return this->supports_two_point_target_temperature_; }
 | 
			
		||||
  bool get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; }
 | 
			
		||||
  void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
 | 
			
		||||
    this->supports_two_point_target_temperature_ = supports_two_point_target_temperature;
 | 
			
		||||
    supports_two_point_target_temperature_ = supports_two_point_target_temperature;
 | 
			
		||||
  }
 | 
			
		||||
  bool get_supports_target_humidity() const { return this->supports_target_humidity_; }
 | 
			
		||||
  bool get_supports_target_humidity() const { return supports_target_humidity_; }
 | 
			
		||||
  void set_supports_target_humidity(bool supports_target_humidity) {
 | 
			
		||||
    this->supports_target_humidity_ = supports_target_humidity;
 | 
			
		||||
    supports_target_humidity_ = supports_target_humidity;
 | 
			
		||||
  }
 | 
			
		||||
  void set_supported_modes(std::set<ClimateMode> modes) { this->supported_modes_ = std::move(modes); }
 | 
			
		||||
  void add_supported_mode(ClimateMode mode) { this->supported_modes_.insert(mode); }
 | 
			
		||||
  void set_supported_modes(std::set<ClimateMode> modes) { supported_modes_ = std::move(modes); }
 | 
			
		||||
  void add_supported_mode(ClimateMode mode) { supported_modes_.insert(mode); }
 | 
			
		||||
  ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
 | 
			
		||||
  void set_supports_auto_mode(bool supports_auto_mode) { set_mode_support_(CLIMATE_MODE_AUTO, supports_auto_mode); }
 | 
			
		||||
  ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
 | 
			
		||||
@@ -72,15 +72,15 @@ class ClimateTraits {
 | 
			
		||||
  }
 | 
			
		||||
  ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
 | 
			
		||||
  void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); }
 | 
			
		||||
  bool supports_mode(ClimateMode mode) const { return this->supported_modes_.count(mode); }
 | 
			
		||||
  const std::set<ClimateMode> &get_supported_modes() const { return this->supported_modes_; }
 | 
			
		||||
  bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); }
 | 
			
		||||
  const std::set<ClimateMode> &get_supported_modes() const { return supported_modes_; }
 | 
			
		||||
 | 
			
		||||
  void set_supports_action(bool supports_action) { this->supports_action_ = supports_action; }
 | 
			
		||||
  bool get_supports_action() const { return this->supports_action_; }
 | 
			
		||||
  void set_supports_action(bool supports_action) { supports_action_ = supports_action; }
 | 
			
		||||
  bool get_supports_action() const { return supports_action_; }
 | 
			
		||||
 | 
			
		||||
  void set_supported_fan_modes(std::set<ClimateFanMode> modes) { this->supported_fan_modes_ = std::move(modes); }
 | 
			
		||||
  void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); }
 | 
			
		||||
  void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.insert(mode); }
 | 
			
		||||
  void set_supported_fan_modes(std::set<ClimateFanMode> modes) { supported_fan_modes_ = std::move(modes); }
 | 
			
		||||
  void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); }
 | 
			
		||||
  void add_supported_custom_fan_mode(const std::string &mode) { supported_custom_fan_modes_.insert(mode); }
 | 
			
		||||
  ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
 | 
			
		||||
  void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); }
 | 
			
		||||
  ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
 | 
			
		||||
@@ -99,37 +99,35 @@ class ClimateTraits {
 | 
			
		||||
  void set_supports_fan_mode_focus(bool supported) { set_fan_mode_support_(CLIMATE_FAN_FOCUS, supported); }
 | 
			
		||||
  ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
 | 
			
		||||
  void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); }
 | 
			
		||||
  bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); }
 | 
			
		||||
  bool get_supports_fan_modes() const {
 | 
			
		||||
    return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty();
 | 
			
		||||
  }
 | 
			
		||||
  const std::set<ClimateFanMode> &get_supported_fan_modes() const { return this->supported_fan_modes_; }
 | 
			
		||||
  bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); }
 | 
			
		||||
  bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); }
 | 
			
		||||
  const std::set<ClimateFanMode> &get_supported_fan_modes() const { return supported_fan_modes_; }
 | 
			
		||||
 | 
			
		||||
  void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) {
 | 
			
		||||
    this->supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
 | 
			
		||||
    supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
 | 
			
		||||
  }
 | 
			
		||||
  const std::set<std::string> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; }
 | 
			
		||||
  const std::set<std::string> &get_supported_custom_fan_modes() const { return supported_custom_fan_modes_; }
 | 
			
		||||
  bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
 | 
			
		||||
    return this->supported_custom_fan_modes_.count(custom_fan_mode);
 | 
			
		||||
    return supported_custom_fan_modes_.count(custom_fan_mode);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_supported_presets(std::set<ClimatePreset> presets) { this->supported_presets_ = std::move(presets); }
 | 
			
		||||
  void add_supported_preset(ClimatePreset preset) { this->supported_presets_.insert(preset); }
 | 
			
		||||
  void add_supported_custom_preset(const std::string &preset) { this->supported_custom_presets_.insert(preset); }
 | 
			
		||||
  bool supports_preset(ClimatePreset preset) const { return this->supported_presets_.count(preset); }
 | 
			
		||||
  bool get_supports_presets() const { return !this->supported_presets_.empty(); }
 | 
			
		||||
  const std::set<climate::ClimatePreset> &get_supported_presets() const { return this->supported_presets_; }
 | 
			
		||||
  void set_supported_presets(std::set<ClimatePreset> presets) { supported_presets_ = std::move(presets); }
 | 
			
		||||
  void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); }
 | 
			
		||||
  void add_supported_custom_preset(const std::string &preset) { supported_custom_presets_.insert(preset); }
 | 
			
		||||
  bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); }
 | 
			
		||||
  bool get_supports_presets() const { return !supported_presets_.empty(); }
 | 
			
		||||
  const std::set<climate::ClimatePreset> &get_supported_presets() const { return supported_presets_; }
 | 
			
		||||
 | 
			
		||||
  void set_supported_custom_presets(std::set<std::string> supported_custom_presets) {
 | 
			
		||||
    this->supported_custom_presets_ = std::move(supported_custom_presets);
 | 
			
		||||
    supported_custom_presets_ = std::move(supported_custom_presets);
 | 
			
		||||
  }
 | 
			
		||||
  const std::set<std::string> &get_supported_custom_presets() const { return this->supported_custom_presets_; }
 | 
			
		||||
  const std::set<std::string> &get_supported_custom_presets() const { return supported_custom_presets_; }
 | 
			
		||||
  bool supports_custom_preset(const std::string &custom_preset) const {
 | 
			
		||||
    return this->supported_custom_presets_.count(custom_preset);
 | 
			
		||||
    return supported_custom_presets_.count(custom_preset);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { this->supported_swing_modes_ = std::move(modes); }
 | 
			
		||||
  void add_supported_swing_mode(ClimateSwingMode mode) { this->supported_swing_modes_.insert(mode); }
 | 
			
		||||
  void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); }
 | 
			
		||||
  void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); }
 | 
			
		||||
  ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
 | 
			
		||||
  void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); }
 | 
			
		||||
  ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
 | 
			
		||||
@@ -140,58 +138,54 @@ class ClimateTraits {
 | 
			
		||||
  void set_supports_swing_mode_horizontal(bool supported) {
 | 
			
		||||
    set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported);
 | 
			
		||||
  }
 | 
			
		||||
  bool supports_swing_mode(ClimateSwingMode swing_mode) const { return this->supported_swing_modes_.count(swing_mode); }
 | 
			
		||||
  bool get_supports_swing_modes() const { return !this->supported_swing_modes_.empty(); }
 | 
			
		||||
  const std::set<ClimateSwingMode> &get_supported_swing_modes() const { return this->supported_swing_modes_; }
 | 
			
		||||
  bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); }
 | 
			
		||||
  bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); }
 | 
			
		||||
  const std::set<ClimateSwingMode> &get_supported_swing_modes() const { return supported_swing_modes_; }
 | 
			
		||||
 | 
			
		||||
  float get_visual_min_temperature() const { return this->visual_min_temperature_; }
 | 
			
		||||
  void set_visual_min_temperature(float visual_min_temperature) {
 | 
			
		||||
    this->visual_min_temperature_ = visual_min_temperature;
 | 
			
		||||
  }
 | 
			
		||||
  float get_visual_max_temperature() const { return this->visual_max_temperature_; }
 | 
			
		||||
  void set_visual_max_temperature(float visual_max_temperature) {
 | 
			
		||||
    this->visual_max_temperature_ = visual_max_temperature;
 | 
			
		||||
  }
 | 
			
		||||
  float get_visual_target_temperature_step() const { return this->visual_target_temperature_step_; }
 | 
			
		||||
  float get_visual_current_temperature_step() const { return this->visual_current_temperature_step_; }
 | 
			
		||||
  float get_visual_min_temperature() const { return visual_min_temperature_; }
 | 
			
		||||
  void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
 | 
			
		||||
  float get_visual_max_temperature() const { return visual_max_temperature_; }
 | 
			
		||||
  void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; }
 | 
			
		||||
  float get_visual_target_temperature_step() const { return visual_target_temperature_step_; }
 | 
			
		||||
  float get_visual_current_temperature_step() const { return visual_current_temperature_step_; }
 | 
			
		||||
  void set_visual_target_temperature_step(float temperature_step) {
 | 
			
		||||
    this->visual_target_temperature_step_ = temperature_step;
 | 
			
		||||
    visual_target_temperature_step_ = temperature_step;
 | 
			
		||||
  }
 | 
			
		||||
  void set_visual_current_temperature_step(float temperature_step) {
 | 
			
		||||
    this->visual_current_temperature_step_ = temperature_step;
 | 
			
		||||
    visual_current_temperature_step_ = temperature_step;
 | 
			
		||||
  }
 | 
			
		||||
  void set_visual_temperature_step(float temperature_step) {
 | 
			
		||||
    this->visual_target_temperature_step_ = temperature_step;
 | 
			
		||||
    this->visual_current_temperature_step_ = temperature_step;
 | 
			
		||||
    visual_target_temperature_step_ = temperature_step;
 | 
			
		||||
    visual_current_temperature_step_ = temperature_step;
 | 
			
		||||
  }
 | 
			
		||||
  int8_t get_target_temperature_accuracy_decimals() const;
 | 
			
		||||
  int8_t get_current_temperature_accuracy_decimals() const;
 | 
			
		||||
 | 
			
		||||
  float get_visual_min_humidity() const { return this->visual_min_humidity_; }
 | 
			
		||||
  void set_visual_min_humidity(float visual_min_humidity) { this->visual_min_humidity_ = visual_min_humidity; }
 | 
			
		||||
  float get_visual_max_humidity() const { return this->visual_max_humidity_; }
 | 
			
		||||
  void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; }
 | 
			
		||||
  float get_visual_min_humidity() const { return visual_min_humidity_; }
 | 
			
		||||
  void set_visual_min_humidity(float visual_min_humidity) { visual_min_humidity_ = visual_min_humidity; }
 | 
			
		||||
  float get_visual_max_humidity() const { return visual_max_humidity_; }
 | 
			
		||||
  void set_visual_max_humidity(float visual_max_humidity) { visual_max_humidity_ = visual_max_humidity; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void set_mode_support_(climate::ClimateMode mode, bool supported) {
 | 
			
		||||
    if (supported) {
 | 
			
		||||
      this->supported_modes_.insert(mode);
 | 
			
		||||
      supported_modes_.insert(mode);
 | 
			
		||||
    } else {
 | 
			
		||||
      this->supported_modes_.erase(mode);
 | 
			
		||||
      supported_modes_.erase(mode);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  void set_fan_mode_support_(climate::ClimateFanMode mode, bool supported) {
 | 
			
		||||
    if (supported) {
 | 
			
		||||
      this->supported_fan_modes_.insert(mode);
 | 
			
		||||
      supported_fan_modes_.insert(mode);
 | 
			
		||||
    } else {
 | 
			
		||||
      this->supported_fan_modes_.erase(mode);
 | 
			
		||||
      supported_fan_modes_.erase(mode);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  void set_swing_mode_support_(climate::ClimateSwingMode mode, bool supported) {
 | 
			
		||||
    if (supported) {
 | 
			
		||||
      this->supported_swing_modes_.insert(mode);
 | 
			
		||||
      supported_swing_modes_.insert(mode);
 | 
			
		||||
    } else {
 | 
			
		||||
      this->supported_swing_modes_.erase(mode);
 | 
			
		||||
      supported_swing_modes_.erase(mode);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,7 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from esphome import core
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import climate, remote_base, sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID, CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT
 | 
			
		||||
from esphome.cpp_generator import MockObjClass
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
from esphome.const import CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["remote_transmitter"]
 | 
			
		||||
AUTO_LOAD = ["sensor", "remote_base"]
 | 
			
		||||
@@ -22,13 +16,8 @@ ClimateIR = climate_ir_ns.class_(
 | 
			
		||||
    remote_base.RemoteTransmittable,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def climate_ir_schema(
 | 
			
		||||
    class_: MockObjClass,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    return (
 | 
			
		||||
        climate.climate_schema(class_)
 | 
			
		||||
        .extend(
 | 
			
		||||
CLIMATE_IR_SCHEMA = (
 | 
			
		||||
    climate.CLIMATE_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
 | 
			
		||||
@@ -39,11 +28,7 @@ def climate_ir_schema(
 | 
			
		||||
    .extend(remote_base.REMOTE_TRANSMITTABLE_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def climate_ir_with_receiver_schema(
 | 
			
		||||
    class_: MockObjClass,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    return climate_ir_schema(class_).extend(
 | 
			
		||||
CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(remote_base.CONF_RECEIVER_ID): cv.use_id(
 | 
			
		||||
            remote_base.RemoteReceiverBase
 | 
			
		||||
@@ -52,28 +37,9 @@ def climate_ir_with_receiver_schema(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
def deprecated_schema_constant(config):
 | 
			
		||||
    type: str = "unknown"
 | 
			
		||||
    if (id := config.get(CONF_ID)) is not None and isinstance(id, core.ID):
 | 
			
		||||
        type = str(id.type).split("::", maxsplit=1)[0]
 | 
			
		||||
    _LOGGER.warning(
 | 
			
		||||
        "Using `climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA` is deprecated and will be removed in ESPHome 2025.11.0. "
 | 
			
		||||
        "Please use `climate_ir.climate_ir_with_receiver_schema(...)` instead. "
 | 
			
		||||
        "If you are seeing this, report an issue to the external_component author and ask them to update it. "
 | 
			
		||||
        "https://developers.esphome.io/blog/2025/05/14/_schema-deprecations/. "
 | 
			
		||||
        "Component using this schema: %s",
 | 
			
		||||
        type,
 | 
			
		||||
    )
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CLIMATE_IR_WITH_RECEIVER_SCHEMA = climate_ir_with_receiver_schema(ClimateIR)
 | 
			
		||||
CLIMATE_IR_WITH_RECEIVER_SCHEMA.add_extra(deprecated_schema_constant)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def register_climate_ir(var, config):
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await climate.register_climate(var, config)
 | 
			
		||||
    await remote_base.register_transmittable(var, config)
 | 
			
		||||
    cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL]))
 | 
			
		||||
    cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT]))
 | 
			
		||||
@@ -82,9 +48,3 @@ async def register_climate_ir(var, config):
 | 
			
		||||
    if sensor_id := config.get(CONF_SENSOR):
 | 
			
		||||
        sens = await cg.get_variable(sensor_id)
 | 
			
		||||
        cg.add(var.set_sensor(sens))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def new_climate_ir(config, *args):
 | 
			
		||||
    var = await climate.new_climate(config, *args)
 | 
			
		||||
    await register_climate_ir(var, config)
 | 
			
		||||
    return var
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import climate_ir
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["climate_ir"]
 | 
			
		||||
 | 
			
		||||
@@ -13,8 +14,9 @@ CONF_BIT_HIGH = "bit_high"
 | 
			
		||||
CONF_BIT_ONE_LOW = "bit_one_low"
 | 
			
		||||
CONF_BIT_ZERO_LOW = "bit_zero_low"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(LgIrClimate).extend(
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(LgIrClimate),
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
            CONF_HEADER_HIGH, default="8000us"
 | 
			
		||||
        ): cv.positive_time_period_microseconds,
 | 
			
		||||
@@ -35,7 +37,8 @@ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(LgIrClimate).extend(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await climate_ir.new_climate_ir(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await climate_ir.register_climate_ir(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_header_high(config[CONF_HEADER_HIGH]))
 | 
			
		||||
    cg.add(var.set_header_low(config[CONF_HEADER_LOW]))
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ const uint32_t FAN_MAX = 0x40;
 | 
			
		||||
 | 
			
		||||
// Temperature
 | 
			
		||||
const uint8_t TEMP_RANGE = TEMP_MAX - TEMP_MIN + 1;
 | 
			
		||||
const uint32_t TEMP_MASK = 0xF00;
 | 
			
		||||
const uint32_t TEMP_MASK = 0XF00;
 | 
			
		||||
const uint32_t TEMP_SHIFT = 8;
 | 
			
		||||
 | 
			
		||||
const uint16_t BITS = 28;
 | 
			
		||||
@@ -43,11 +43,11 @@ void LgIrClimate::transmit_state() {
 | 
			
		||||
  // ESP_LOGD(TAG, "climate_lg_ir mode_before_ code: 0x%02X", modeBefore_);
 | 
			
		||||
 | 
			
		||||
  // Set command
 | 
			
		||||
  if (this->send_swing_cmd_) {
 | 
			
		||||
    this->send_swing_cmd_ = false;
 | 
			
		||||
  if (send_swing_cmd_) {
 | 
			
		||||
    send_swing_cmd_ = false;
 | 
			
		||||
    remote_state |= COMMAND_SWING;
 | 
			
		||||
  } else {
 | 
			
		||||
    bool climate_is_off = (this->mode_before_ == climate::CLIMATE_MODE_OFF);
 | 
			
		||||
    bool climate_is_off = (mode_before_ == climate::CLIMATE_MODE_OFF);
 | 
			
		||||
    switch (this->mode) {
 | 
			
		||||
      case climate::CLIMATE_MODE_COOL:
 | 
			
		||||
        remote_state |= climate_is_off ? COMMAND_ON_COOL : COMMAND_COOL;
 | 
			
		||||
@@ -71,7 +71,7 @@ void LgIrClimate::transmit_state() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->mode_before_ = this->mode;
 | 
			
		||||
  mode_before_ = this->mode;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode);
 | 
			
		||||
 | 
			
		||||
@@ -102,7 +102,7 @@ void LgIrClimate::transmit_state() {
 | 
			
		||||
    remote_state |= ((temp - 15) << TEMP_SHIFT);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->transmit_(remote_state);
 | 
			
		||||
  transmit_(remote_state);
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -187,7 +187,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LgIrClimate::transmit_(uint32_t value) {
 | 
			
		||||
  this->calc_checksum_(value);
 | 
			
		||||
  calc_checksum_(value);
 | 
			
		||||
  ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02" PRIX32, value);
 | 
			
		||||
 | 
			
		||||
  auto transmit = this->transmitter_->transmit();
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ class LgIrClimate : public climate_ir::ClimateIR {
 | 
			
		||||
 | 
			
		||||
  /// Override control to change settings of the climate device.
 | 
			
		||||
  void control(const climate::ClimateCall &call) override {
 | 
			
		||||
    this->send_swing_cmd_ = call.get_swing_mode().has_value();
 | 
			
		||||
    send_swing_cmd_ = call.get_swing_mode().has_value();
 | 
			
		||||
    // swing resets after unit powered off
 | 
			
		||||
    if (call.get_mode().has_value() && *call.get_mode() == climate::CLIMATE_MODE_OFF)
 | 
			
		||||
      this->swing_mode = climate::CLIMATE_SWING_OFF;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import climate_ir
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["climate_ir"]
 | 
			
		||||
CODEOWNERS = ["@glmnet"]
 | 
			
		||||
@@ -7,8 +9,13 @@ CODEOWNERS = ["@glmnet"]
 | 
			
		||||
coolix_ns = cg.esphome_ns.namespace("coolix")
 | 
			
		||||
CoolixClimate = coolix_ns.class_("CoolixClimate", climate_ir.ClimateIR)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(CoolixClimate)
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CoolixClimate),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    await climate_ir.new_climate_ir(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await climate_ir.register_climate_ir(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_DEVICE_CLASS,
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_SOURCE_ID,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core.entity_helpers import inherit_property_from
 | 
			
		||||
@@ -14,15 +15,12 @@ from .. import copy_ns
 | 
			
		||||
CopyCover = copy_ns.class_("CopyCover", cover.Cover, cg.Component)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cover.cover_schema(CopyCover)
 | 
			
		||||
    .extend(
 | 
			
		||||
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CopyCover),
 | 
			
		||||
        cv.Required(CONF_SOURCE_ID): cv.use_id(cover.Cover),
 | 
			
		||||
    }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
    inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
 | 
			
		||||
@@ -32,7 +30,8 @@ FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await cover.new_cover(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cover.register_cover(var, config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    source = await cg.get_variable(config[CONF_SOURCE_ID])
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import fan
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_SOURCE_ID
 | 
			
		||||
from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_ID, CONF_SOURCE_ID
 | 
			
		||||
from esphome.core.entity_helpers import inherit_property_from
 | 
			
		||||
 | 
			
		||||
from .. import copy_ns
 | 
			
		||||
@@ -9,15 +9,12 @@ from .. import copy_ns
 | 
			
		||||
CopyFan = copy_ns.class_("CopyFan", fan.Fan, cg.Component)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    fan.fan_schema(CopyFan)
 | 
			
		||||
    .extend(
 | 
			
		||||
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CopyFan),
 | 
			
		||||
        cv.Required(CONF_SOURCE_ID): cv.use_id(fan.Fan),
 | 
			
		||||
    }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
    inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
 | 
			
		||||
@@ -26,7 +23,8 @@ FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await fan.new_fan(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await fan.register_fan(var, config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    source = await cg.get_variable(config[CONF_SOURCE_ID])
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import lock
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_SOURCE_ID
 | 
			
		||||
from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_ID, CONF_SOURCE_ID
 | 
			
		||||
from esphome.core.entity_helpers import inherit_property_from
 | 
			
		||||
 | 
			
		||||
from .. import copy_ns
 | 
			
		||||
@@ -9,15 +9,12 @@ from .. import copy_ns
 | 
			
		||||
CopyLock = copy_ns.class_("CopyLock", lock.Lock, cg.Component)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    lock.lock_schema(CopyLock)
 | 
			
		||||
    .extend(
 | 
			
		||||
CONFIG_SCHEMA = lock.LOCK_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CopyLock),
 | 
			
		||||
        cv.Required(CONF_SOURCE_ID): cv.use_id(lock.Lock),
 | 
			
		||||
    }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
    inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
 | 
			
		||||
@@ -26,7 +23,8 @@ FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await lock.new_lock(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await lock.register_lock(var, config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    source = await cg.get_variable(config[CONF_SOURCE_ID])
 | 
			
		||||
 
 | 
			
		||||
@@ -9,15 +9,12 @@ from .. import copy_ns
 | 
			
		||||
CopyText = copy_ns.class_("CopyText", text.Text, cg.Component)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    text.text_schema(CopyText)
 | 
			
		||||
    .extend(
 | 
			
		||||
CONFIG_SCHEMA = text.TEXT_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CopyText),
 | 
			
		||||
        cv.Required(CONF_SOURCE_ID): cv.use_id(text.Text),
 | 
			
		||||
    }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
    inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,6 @@ from esphome.components import mqtt, web_server
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DEVICE_CLASS,
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_ON_OPEN,
 | 
			
		||||
@@ -33,7 +31,6 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_WINDOW,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.cpp_generator import MockObjClass
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
@@ -92,11 +89,12 @@ CoverClosedTrigger = cover_ns.class_(
 | 
			
		||||
 | 
			
		||||
CONF_ON_CLOSED = "on_closed"
 | 
			
		||||
 | 
			
		||||
_COVER_SCHEMA = (
 | 
			
		||||
COVER_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Cover),
 | 
			
		||||
            cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
 | 
			
		||||
            cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
 | 
			
		||||
            cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
 | 
			
		||||
@@ -126,33 +124,6 @@ _COVER_SCHEMA = (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def cover_schema(
 | 
			
		||||
    class_: MockObjClass,
 | 
			
		||||
    *,
 | 
			
		||||
    device_class: str = cv.UNDEFINED,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(class_),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for key, default, validator in [
 | 
			
		||||
        (CONF_DEVICE_CLASS, device_class, cv.one_of(*DEVICE_CLASSES, lower=True)),
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_ICON, icon, cv.icon),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return _COVER_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
COVER_SCHEMA = cover_schema(Cover)
 | 
			
		||||
COVER_SCHEMA.add_extra(cv.deprecated_schema_constant("cover"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_cover_core_(var, config):
 | 
			
		||||
    await setup_entity(var, config)
 | 
			
		||||
 | 
			
		||||
@@ -192,12 +163,6 @@ async def register_cover(var, config):
 | 
			
		||||
    await setup_cover_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def new_cover(config, *args):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], *args)
 | 
			
		||||
    await register_cover(var, config)
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
COVER_ACTION_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(Cover),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
#include "cse7766.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace cse7766 {
 | 
			
		||||
@@ -8,7 +7,7 @@ namespace cse7766 {
 | 
			
		||||
static const char *const TAG = "cse7766";
 | 
			
		||||
 | 
			
		||||
void CSE7766Component::loop() {
 | 
			
		||||
  const uint32_t now = App.get_loop_component_start_time();
 | 
			
		||||
  const uint32_t now = millis();
 | 
			
		||||
  if (now - this->last_transmission_ >= 500) {
 | 
			
		||||
    // last transmission too long ago. Reset RX index.
 | 
			
		||||
    this->raw_data_index_ = 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import binary_sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
from .. import cst226_ns
 | 
			
		||||
from ..touchscreen import CST226ButtonListener, CST226Touchscreen
 | 
			
		||||
 | 
			
		||||
CONF_CST226_ID = "cst226_id"
 | 
			
		||||
 | 
			
		||||
CST226Button = cst226_ns.class_(
 | 
			
		||||
    "CST226Button",
 | 
			
		||||
    binary_sensor.BinarySensor,
 | 
			
		||||
    cg.Component,
 | 
			
		||||
    CST226ButtonListener,
 | 
			
		||||
    cg.Parented.template(CST226Touchscreen),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(CST226Button).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_CST226_ID): cv.use_id(CST226Touchscreen),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await binary_sensor.new_binary_sensor(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await cg.register_parented(var, config[CONF_CST226_ID])
 | 
			
		||||
@@ -1,22 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
#include "../touchscreen/cst226_touchscreen.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace cst226 {
 | 
			
		||||
 | 
			
		||||
class CST226Button : public binary_sensor::BinarySensor,
 | 
			
		||||
                     public Component,
 | 
			
		||||
                     public CST226ButtonListener,
 | 
			
		||||
                     public Parented<CST226Touchscreen> {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  void update_button(bool state) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace cst226
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
#include "cs226_button.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace cst226 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "CST226.binary_sensor";
 | 
			
		||||
 | 
			
		||||
void CST226Button::setup() {
 | 
			
		||||
  this->parent_->register_button_listener(this);
 | 
			
		||||
  this->publish_initial_state(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CST226Button::dump_config() { LOG_BINARY_SENSOR("", "CST226 Button", this); }
 | 
			
		||||
 | 
			
		||||
void CST226Button::update_button(bool state) { this->publish_state(state); }
 | 
			
		||||
 | 
			
		||||
}  // namespace cst226
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -3,10 +3,8 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace cst226 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "cst226.touchscreen";
 | 
			
		||||
 | 
			
		||||
void CST226Touchscreen::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up CST226 Touchscreen...");
 | 
			
		||||
  esph_log_config(TAG, "Setting up CST226 Touchscreen...");
 | 
			
		||||
  if (this->reset_pin_ != nullptr) {
 | 
			
		||||
    this->reset_pin_->setup();
 | 
			
		||||
    this->reset_pin_->digital_write(true);
 | 
			
		||||
@@ -28,11 +26,6 @@ void CST226Touchscreen::update_touches() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
  if (data[0] == 0x83 && data[1] == 0x17 && data[5] == 0x80) {
 | 
			
		||||
    this->update_button_state_(true);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->update_button_state_(false);
 | 
			
		||||
  if (data[6] != 0xAB || data[0] == 0xAB || data[5] == 0x80) {
 | 
			
		||||
    this->skip_update_ = true;
 | 
			
		||||
    return;
 | 
			
		||||
@@ -50,21 +43,13 @@ void CST226Touchscreen::update_touches() {
 | 
			
		||||
    int16_t y = (data[index + 2] << 4) | (data[index + 3] & 0x0F);
 | 
			
		||||
    int16_t z = data[index + 4];
 | 
			
		||||
    this->add_raw_touch_position_(id, x, y, z);
 | 
			
		||||
    ESP_LOGV(TAG, "Read touch %d: %d/%d", id, x, y);
 | 
			
		||||
    esph_log_v(TAG, "Read touch %d: %d/%d", id, x, y);
 | 
			
		||||
    index += 5;
 | 
			
		||||
    if (i == 0)
 | 
			
		||||
      index += 2;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool CST226Touchscreen::read16_(uint16_t addr, uint8_t *data, size_t len) {
 | 
			
		||||
  if (this->read_register16(addr, data, len) != i2c::ERROR_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "Read data from 0x%04X failed", addr);
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
void CST226Touchscreen::continue_setup_() {
 | 
			
		||||
  uint8_t buffer[8];
 | 
			
		||||
  if (this->interrupt_pin_ != nullptr) {
 | 
			
		||||
@@ -73,7 +58,7 @@ void CST226Touchscreen::continue_setup_() {
 | 
			
		||||
  }
 | 
			
		||||
  buffer[0] = 0xD1;
 | 
			
		||||
  if (this->write_register16(0xD1, buffer, 1) != i2c::ERROR_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "Write byte to 0xD1 failed");
 | 
			
		||||
    esph_log_e(TAG, "Write byte to 0xD1 failed");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
@@ -81,7 +66,7 @@ void CST226Touchscreen::continue_setup_() {
 | 
			
		||||
  if (this->read16_(0xD204, buffer, 4)) {
 | 
			
		||||
    uint16_t chip_id = buffer[2] + (buffer[3] << 8);
 | 
			
		||||
    uint16_t project_id = buffer[0] + (buffer[1] << 8);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "Chip ID %X, project ID %x", chip_id, project_id);
 | 
			
		||||
    esph_log_config(TAG, "Chip ID %X, project ID %x", chip_id, project_id);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->x_raw_max_ == 0 || this->y_raw_max_ == 0) {
 | 
			
		||||
    if (this->read16_(0xD1F8, buffer, 4)) {
 | 
			
		||||
@@ -95,14 +80,7 @@ void CST226Touchscreen::continue_setup_() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  this->setup_complete_ = true;
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "CST226 Touchscreen setup complete");
 | 
			
		||||
}
 | 
			
		||||
void CST226Touchscreen::update_button_state_(bool state) {
 | 
			
		||||
  if (this->button_touched_ == state)
 | 
			
		||||
    return;
 | 
			
		||||
  this->button_touched_ = state;
 | 
			
		||||
  for (auto *listener : this->button_listeners_)
 | 
			
		||||
    listener->update_button(state);
 | 
			
		||||
  esph_log_config(TAG, "CST226 Touchscreen setup complete");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CST226Touchscreen::dump_config() {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,12 +9,9 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace cst226 {
 | 
			
		||||
 | 
			
		||||
static const uint8_t CST226_REG_STATUS = 0x00;
 | 
			
		||||
static const char *const TAG = "cst226.touchscreen";
 | 
			
		||||
 | 
			
		||||
class CST226ButtonListener {
 | 
			
		||||
 public:
 | 
			
		||||
  virtual void update_button(bool state) = 0;
 | 
			
		||||
};
 | 
			
		||||
static const uint8_t CST226_REG_STATUS = 0x00;
 | 
			
		||||
 | 
			
		||||
class CST226Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
@@ -25,19 +22,22 @@ class CST226Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
 | 
			
		||||
  void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
 | 
			
		||||
  void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
 | 
			
		||||
  bool can_proceed() override { return this->setup_complete_ || this->is_failed(); }
 | 
			
		||||
  void register_button_listener(CST226ButtonListener *listener) { this->button_listeners_.push_back(listener); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool read16_(uint16_t addr, uint8_t *data, size_t len);
 | 
			
		||||
  bool read16_(uint16_t addr, uint8_t *data, size_t len) {
 | 
			
		||||
    if (this->read_register16(addr, data, len) != i2c::ERROR_OK) {
 | 
			
		||||
      esph_log_e(TAG, "Read data from 0x%04X failed", addr);
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  void continue_setup_();
 | 
			
		||||
  void update_button_state_(bool state);
 | 
			
		||||
 | 
			
		||||
  InternalGPIOPin *interrupt_pin_{};
 | 
			
		||||
  GPIOPin *reset_pin_{};
 | 
			
		||||
  uint8_t chip_id_{};
 | 
			
		||||
  bool setup_complete_{};
 | 
			
		||||
  std::vector<CST226ButtonListener *> button_listeners_;
 | 
			
		||||
  bool button_touched_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace cst226
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_CLOSE_ACTION,
 | 
			
		||||
    CONF_CLOSE_DURATION,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MAX_DURATION,
 | 
			
		||||
    CONF_OPEN_ACTION,
 | 
			
		||||
    CONF_OPEN_DURATION,
 | 
			
		||||
@@ -29,10 +30,9 @@ CurrentBasedCover = current_based_ns.class_(
 | 
			
		||||
    "CurrentBasedCover", cover.Cover, cg.Component
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cover.cover_schema(CurrentBasedCover)
 | 
			
		||||
    .extend(
 | 
			
		||||
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CurrentBasedCover),
 | 
			
		||||
        cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
        cv.Required(CONF_OPEN_SENSOR): cv.use_id(sensor.Sensor),
 | 
			
		||||
        cv.Required(CONF_OPEN_MOVING_CURRENT_THRESHOLD): cv.float_range(
 | 
			
		||||
@@ -62,14 +62,13 @@ CONFIG_SCHEMA = (
 | 
			
		||||
            CONF_START_SENSING_DELAY, default="500ms"
 | 
			
		||||
        ): cv.positive_time_period_milliseconds,
 | 
			
		||||
    }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await cover.new_cover(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await cover.register_cover(var, config)
 | 
			
		||||
 | 
			
		||||
    await automation.build_automation(
 | 
			
		||||
        var.get_stop_trigger(), [], config[CONF_STOP_ACTION]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
#include "current_based_cover.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include <cfloat>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -61,7 +60,7 @@ void CurrentBasedCover::loop() {
 | 
			
		||||
  if (this->current_operation == COVER_OPERATION_IDLE)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  const uint32_t now = App.get_loop_component_start_time();
 | 
			
		||||
  const uint32_t now = millis();
 | 
			
		||||
 | 
			
		||||
  if (this->current_operation == COVER_OPERATION_OPENING) {
 | 
			
		||||
    if (this->malfunction_detection_ && this->is_closing_()) {  // Malfunction
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,20 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import climate_ir
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["climate_ir"]
 | 
			
		||||
 | 
			
		||||
daikin_ns = cg.esphome_ns.namespace("daikin")
 | 
			
		||||
DaikinClimate = daikin_ns.class_("DaikinClimate", climate_ir.ClimateIR)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinClimate)
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(DaikinClimate),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    await climate_ir.new_climate_ir(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await climate_ir.register_climate_ir(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,7 @@ void DaikinClimate::transmit_state() {
 | 
			
		||||
  transmit.perform();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t DaikinClimate::operation_mode_() const {
 | 
			
		||||
uint8_t DaikinClimate::operation_mode_() {
 | 
			
		||||
  uint8_t operating_mode = DAIKIN_MODE_ON;
 | 
			
		||||
  switch (this->mode) {
 | 
			
		||||
    case climate::CLIMATE_MODE_COOL:
 | 
			
		||||
@@ -92,12 +92,9 @@ uint8_t DaikinClimate::operation_mode_() const {
 | 
			
		||||
  return operating_mode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t DaikinClimate::fan_speed_() const {
 | 
			
		||||
uint16_t DaikinClimate::fan_speed_() {
 | 
			
		||||
  uint16_t fan_speed;
 | 
			
		||||
  switch (this->fan_mode.value()) {
 | 
			
		||||
    case climate::CLIMATE_FAN_QUIET:
 | 
			
		||||
      fan_speed = DAIKIN_FAN_SILENT << 8;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_LOW:
 | 
			
		||||
      fan_speed = DAIKIN_FAN_1 << 8;
 | 
			
		||||
      break;
 | 
			
		||||
@@ -129,11 +126,12 @@ uint16_t DaikinClimate::fan_speed_() const {
 | 
			
		||||
  return fan_speed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t DaikinClimate::temperature_() const {
 | 
			
		||||
uint8_t DaikinClimate::temperature_() {
 | 
			
		||||
  // Force special temperatures depending on the mode
 | 
			
		||||
  switch (this->mode) {
 | 
			
		||||
    case climate::CLIMATE_MODE_FAN_ONLY:
 | 
			
		||||
      return 0x32;
 | 
			
		||||
    case climate::CLIMATE_MODE_HEAT_COOL:
 | 
			
		||||
    case climate::CLIMATE_MODE_DRY:
 | 
			
		||||
      return 0xc0;
 | 
			
		||||
    default:
 | 
			
		||||
@@ -150,25 +148,19 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
 | 
			
		||||
  if (frame[DAIKIN_STATE_FRAME_SIZE - 1] != checksum)
 | 
			
		||||
    return false;
 | 
			
		||||
  uint8_t mode = frame[5];
 | 
			
		||||
  // Temperature is given in degrees celcius * 2
 | 
			
		||||
  // only update for states that use the temperature
 | 
			
		||||
  uint8_t temperature = frame[6];
 | 
			
		||||
  if (mode & DAIKIN_MODE_ON) {
 | 
			
		||||
    switch (mode & 0xF0) {
 | 
			
		||||
      case DAIKIN_MODE_COOL:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_COOL;
 | 
			
		||||
        this->target_temperature = static_cast<float>(temperature * 0.5f);
 | 
			
		||||
        break;
 | 
			
		||||
      case DAIKIN_MODE_DRY:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_DRY;
 | 
			
		||||
        break;
 | 
			
		||||
      case DAIKIN_MODE_HEAT:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_HEAT;
 | 
			
		||||
        this->target_temperature = static_cast<float>(temperature * 0.5f);
 | 
			
		||||
        break;
 | 
			
		||||
      case DAIKIN_MODE_AUTO:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_HEAT_COOL;
 | 
			
		||||
        this->target_temperature = static_cast<float>(temperature * 0.5f);
 | 
			
		||||
        break;
 | 
			
		||||
      case DAIKIN_MODE_FAN:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_FAN_ONLY;
 | 
			
		||||
@@ -177,6 +169,10 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
 | 
			
		||||
  } else {
 | 
			
		||||
    this->mode = climate::CLIMATE_MODE_OFF;
 | 
			
		||||
  }
 | 
			
		||||
  uint8_t temperature = frame[6];
 | 
			
		||||
  if (!(temperature & 0xC0)) {
 | 
			
		||||
    this->target_temperature = temperature >> 1;
 | 
			
		||||
  }
 | 
			
		||||
  uint8_t fan_mode = frame[8];
 | 
			
		||||
  uint8_t swing_mode = frame[9];
 | 
			
		||||
  if (fan_mode & 0xF && swing_mode & 0xF) {
 | 
			
		||||
@@ -191,6 +187,7 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
 | 
			
		||||
  switch (fan_mode & 0xF0) {
 | 
			
		||||
    case DAIKIN_FAN_1:
 | 
			
		||||
    case DAIKIN_FAN_2:
 | 
			
		||||
    case DAIKIN_FAN_SILENT:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_LOW;
 | 
			
		||||
      break;
 | 
			
		||||
    case DAIKIN_FAN_3:
 | 
			
		||||
@@ -203,9 +200,6 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
 | 
			
		||||
    case DAIKIN_FAN_AUTO:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
			
		||||
      break;
 | 
			
		||||
    case DAIKIN_FAN_SILENT:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_QUIET;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
  return true;
 | 
			
		||||
 
 | 
			
		||||
@@ -44,17 +44,17 @@ class DaikinClimate : public climate_ir::ClimateIR {
 | 
			
		||||
 public:
 | 
			
		||||
  DaikinClimate()
 | 
			
		||||
      : climate_ir::ClimateIR(DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true,
 | 
			
		||||
                              {climate::CLIMATE_FAN_QUIET, climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW,
 | 
			
		||||
                               climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
 | 
			
		||||
                              {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;
 | 
			
		||||
  uint8_t operation_mode_() const;
 | 
			
		||||
  uint16_t fan_speed_() const;
 | 
			
		||||
  uint8_t temperature_() const;
 | 
			
		||||
  uint8_t operation_mode_();
 | 
			
		||||
  uint16_t fan_speed_();
 | 
			
		||||
  uint8_t temperature_();
 | 
			
		||||
  // Handle received IR Buffer
 | 
			
		||||
  bool on_receive(remote_base::RemoteReceiveData data) override;
 | 
			
		||||
  bool parse_state_frame_(const uint8_t frame[]);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,18 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import climate_ir
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["climate_ir"]
 | 
			
		||||
 | 
			
		||||
daikin_arc_ns = cg.esphome_ns.namespace("daikin_arc")
 | 
			
		||||
DaikinArcClimate = daikin_arc_ns.class_("DaikinArcClimate", climate_ir.ClimateIR)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinArcClimate)
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
 | 
			
		||||
    {cv.GenerateID(): cv.declare_id(DaikinArcClimate)}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    await climate_ir.new_climate_ir(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await climate_ir.register_climate_ir(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import climate_ir
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_USE_FAHRENHEIT
 | 
			
		||||
from esphome.const import CONF_ID, CONF_USE_FAHRENHEIT
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["climate_ir"]
 | 
			
		||||
 | 
			
		||||
@@ -9,13 +9,15 @@ daikin_brc_ns = cg.esphome_ns.namespace("daikin_brc")
 | 
			
		||||
DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinBrcClimate).extend(
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(DaikinBrcClimate),
 | 
			
		||||
        cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await climate_ir.new_climate_ir(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await climate_ir.register_climate_ir(var, config)
 | 
			
		||||
    cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT]))
 | 
			
		||||
 
 | 
			
		||||
@@ -56,13 +56,21 @@ void DallasTemperatureSensor::update() {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DallasTemperatureSensor::read_scratch_pad_() {
 | 
			
		||||
  bool success = this->send_command_(DALLAS_COMMAND_READ_SCRATCH_PAD);
 | 
			
		||||
  if (success) {
 | 
			
		||||
void IRAM_ATTR DallasTemperatureSensor::read_scratch_pad_int_() {
 | 
			
		||||
  for (uint8_t &i : this->scratch_pad_) {
 | 
			
		||||
    i = this->bus_->read8();
 | 
			
		||||
  }
 | 
			
		||||
  } else {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DallasTemperatureSensor::read_scratch_pad_() {
 | 
			
		||||
  bool success;
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    success = this->send_command_(DALLAS_COMMAND_READ_SCRATCH_PAD);
 | 
			
		||||
    if (success)
 | 
			
		||||
      this->read_scratch_pad_int_();
 | 
			
		||||
  }
 | 
			
		||||
  if (!success) {
 | 
			
		||||
    ESP_LOGW(TAG, "'%s' - reading scratch pad failed bus reset", this->get_name().c_str());
 | 
			
		||||
    this->status_set_warning("bus reset failed");
 | 
			
		||||
  }
 | 
			
		||||
@@ -105,6 +113,8 @@ void DallasTemperatureSensor::setup() {
 | 
			
		||||
    return;
 | 
			
		||||
  this->scratch_pad_[4] = res;
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    if (this->send_command_(DALLAS_COMMAND_WRITE_SCRATCH_PAD)) {
 | 
			
		||||
      this->bus_->write8(this->scratch_pad_[2]);  // high alarm temp
 | 
			
		||||
      this->bus_->write8(this->scratch_pad_[3]);  // low alarm temp
 | 
			
		||||
@@ -114,6 +124,7 @@ void DallasTemperatureSensor::setup() {
 | 
			
		||||
    // write value to EEPROM
 | 
			
		||||
    this->send_command_(DALLAS_COMMAND_COPY_SCRATCH_PAD);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DallasTemperatureSensor::check_scratch_pad_() {
 | 
			
		||||
  bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
 | 
			
		||||
@@ -127,10 +138,6 @@ bool DallasTemperatureSensor::check_scratch_pad_() {
 | 
			
		||||
  if (!chksum_validity) {
 | 
			
		||||
    ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
 | 
			
		||||
    this->status_set_warning("scratch pad checksum invalid");
 | 
			
		||||
    ESP_LOGD(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
 | 
			
		||||
             this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
 | 
			
		||||
             this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
 | 
			
		||||
             crc8(this->scratch_pad_, 8));
 | 
			
		||||
  }
 | 
			
		||||
  return chksum_validity;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ class DallasTemperatureSensor : public PollingComponent, public sensor::Sensor,
 | 
			
		||||
  /// Get the number of milliseconds we have to wait for the conversion phase.
 | 
			
		||||
  uint16_t millis_to_wait_for_conversion_() const;
 | 
			
		||||
  bool read_scratch_pad_();
 | 
			
		||||
  void read_scratch_pad_int_();
 | 
			
		||||
  bool check_scratch_pad_();
 | 
			
		||||
  float get_temp_c_();
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
#include "daly_bms.h"
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace daly_bms {
 | 
			
		||||
@@ -33,7 +32,7 @@ void DalyBmsComponent::update() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DalyBmsComponent::loop() {
 | 
			
		||||
  const uint32_t now = App.get_loop_component_start_time();
 | 
			
		||||
  const uint32_t now = millis();
 | 
			
		||||
  if (this->receiving_ && (now - this->last_transmission_ >= 200)) {
 | 
			
		||||
    // last transmission too long ago. Reset RX index.
 | 
			
		||||
    ESP_LOGW(TAG, "Last transmission too long ago. Reset RX index.");
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
#include "debug_component.h"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
@@ -26,7 +25,6 @@ void DebugComponent::dump_config() {
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  LOG_SENSOR("  ", "Free space on heap", this->free_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Largest free heap block", this->block_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "CPU frequency", this->cpu_frequency_sensor_);
 | 
			
		||||
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
 | 
			
		||||
  LOG_SENSOR("  ", "Heap fragmentation", this->fragmentation_sensor_);
 | 
			
		||||
#endif  // defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
 | 
			
		||||
@@ -70,7 +68,7 @@ void DebugComponent::loop() {
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  // calculate loop time - from last call to this one
 | 
			
		||||
  if (this->loop_time_sensor_ != nullptr) {
 | 
			
		||||
    uint32_t now = App.get_loop_component_start_time();
 | 
			
		||||
    uint32_t now = millis();
 | 
			
		||||
    uint32_t loop_time = now - this->last_loop_timetag_;
 | 
			
		||||
    this->max_loop_time_ = std::max(this->max_loop_time_, loop_time);
 | 
			
		||||
    this->last_loop_timetag_ = now;
 | 
			
		||||
@@ -88,9 +86,6 @@ void DebugComponent::update() {
 | 
			
		||||
    this->loop_time_sensor_->publish_state(this->max_loop_time_);
 | 
			
		||||
    this->max_loop_time_ = 0;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->cpu_frequency_sensor_ != nullptr) {
 | 
			
		||||
    this->cpu_frequency_sensor_->publish_state(arch_get_cpu_freq_hz());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#endif  // USE_SENSOR
 | 
			
		||||
  update_platform_();
 | 
			
		||||
 
 | 
			
		||||
@@ -36,13 +36,7 @@ class DebugComponent : public PollingComponent {
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  void set_psram_sensor(sensor::Sensor *psram_sensor) { this->psram_sensor_ = psram_sensor; }
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
  void set_cpu_frequency_sensor(sensor::Sensor *cpu_frequency_sensor) {
 | 
			
		||||
    this->cpu_frequency_sensor_ = cpu_frequency_sensor;
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_SENSOR
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  void on_shutdown() override;
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 protected:
 | 
			
		||||
  uint32_t free_heap_{};
 | 
			
		||||
 | 
			
		||||
@@ -59,7 +53,6 @@ class DebugComponent : public PollingComponent {
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  sensor::Sensor *psram_sensor_{nullptr};
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
  sensor::Sensor *cpu_frequency_sensor_{nullptr};
 | 
			
		||||
#endif  // USE_SENSOR
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
@@ -82,7 +75,6 @@ class DebugComponent : public PollingComponent {
 | 
			
		||||
#endif  // USE_TEXT_SENSOR
 | 
			
		||||
 | 
			
		||||
  std::string get_reset_reason_();
 | 
			
		||||
  std::string get_wakeup_cause_();
 | 
			
		||||
  uint32_t get_free_heap_();
 | 
			
		||||
  void get_device_info_(std::string &device_info);
 | 
			
		||||
  void update_platform_();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,25 @@
 | 
			
		||||
#include "debug_component.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include <esp_sleep.h>
 | 
			
		||||
 | 
			
		||||
#include <esp_heap_caps.h>
 | 
			
		||||
#include <esp_system.h>
 | 
			
		||||
#include <esp_chip_info.h>
 | 
			
		||||
#include <esp_partition.h>
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32)
 | 
			
		||||
#include <esp32/rom/rtc.h>
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32C3)
 | 
			
		||||
#include <esp32c3/rom/rtc.h>
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
#include <esp32c6/rom/rtc.h>
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32S2)
 | 
			
		||||
#include <esp32s2/rom/rtc.h>
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
#include <esp32s3/rom/rtc.h>
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32H2)
 | 
			
		||||
#include <esp32h2/rom/rtc.h>
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#include <Esp.h>
 | 
			
		||||
#endif
 | 
			
		||||
@@ -22,90 +29,6 @@ namespace debug {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "debug";
 | 
			
		||||
 | 
			
		||||
// index by values returned by esp_reset_reason
 | 
			
		||||
 | 
			
		||||
static const char *const RESET_REASONS[] = {
 | 
			
		||||
    "unknown source",
 | 
			
		||||
    "power-on event",
 | 
			
		||||
    "external pin",
 | 
			
		||||
    "software via esp_restart",
 | 
			
		||||
    "exception/panic",
 | 
			
		||||
    "interrupt watchdog",
 | 
			
		||||
    "task watchdog",
 | 
			
		||||
    "other watchdogs",
 | 
			
		||||
    "exiting deep sleep mode",
 | 
			
		||||
    "brownout",
 | 
			
		||||
    "SDIO",
 | 
			
		||||
    "USB peripheral",
 | 
			
		||||
    "JTAG",
 | 
			
		||||
    "efuse error",
 | 
			
		||||
    "power glitch detected",
 | 
			
		||||
    "CPU lock up",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const char *const REBOOT_KEY = "reboot_source";
 | 
			
		||||
static const size_t REBOOT_MAX_LEN = 24;
 | 
			
		||||
 | 
			
		||||
// on shutdown, store the source of the reboot request
 | 
			
		||||
void DebugComponent::on_shutdown() {
 | 
			
		||||
  auto *component = App.get_current_component();
 | 
			
		||||
  char buffer[REBOOT_MAX_LEN]{};
 | 
			
		||||
  auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
 | 
			
		||||
  if (component != nullptr) {
 | 
			
		||||
    strncpy(buffer, component->get_component_source(), REBOOT_MAX_LEN - 1);
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Storing reboot source: %s", buffer);
 | 
			
		||||
  pref.save(&buffer);
 | 
			
		||||
  global_preferences->sync();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string DebugComponent::get_reset_reason_() {
 | 
			
		||||
  std::string reset_reason;
 | 
			
		||||
  unsigned reason = esp_reset_reason();
 | 
			
		||||
  if (reason < sizeof(RESET_REASONS) / sizeof(RESET_REASONS[0])) {
 | 
			
		||||
    reset_reason = RESET_REASONS[reason];
 | 
			
		||||
    if (reason == ESP_RST_SW) {
 | 
			
		||||
      auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
 | 
			
		||||
      char buffer[REBOOT_MAX_LEN]{};
 | 
			
		||||
      if (pref.load(&buffer)) {
 | 
			
		||||
        reset_reason = "Reboot request from " + std::string(buffer);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    reset_reason = "unknown source";
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
 | 
			
		||||
  return reset_reason;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char *const WAKEUP_CAUSES[] = {
 | 
			
		||||
    "undefined",
 | 
			
		||||
    "undefined",
 | 
			
		||||
    "external signal using RTC_IO",
 | 
			
		||||
    "external signal using RTC_CNTL",
 | 
			
		||||
    "timer",
 | 
			
		||||
    "touchpad",
 | 
			
		||||
    "ULP program",
 | 
			
		||||
    "GPIO",
 | 
			
		||||
    "UART",
 | 
			
		||||
    "WIFI",
 | 
			
		||||
    "COCPU int",
 | 
			
		||||
    "COCPU crash",
 | 
			
		||||
    "BT",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::string DebugComponent::get_wakeup_cause_() {
 | 
			
		||||
  const char *wake_reason;
 | 
			
		||||
  unsigned reason = esp_sleep_get_wakeup_cause();
 | 
			
		||||
  if (reason < sizeof(WAKEUP_CAUSES) / sizeof(WAKEUP_CAUSES[0])) {
 | 
			
		||||
    wake_reason = WAKEUP_CAUSES[reason];
 | 
			
		||||
  } else {
 | 
			
		||||
    wake_reason = "unknown source";
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Wakeup Reason: %s", wake_reason);
 | 
			
		||||
  return wake_reason;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DebugComponent::log_partition_info_() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Partition table:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  %-12s %-4s %-8s %-10s %-10s", "Name", "Type", "Subtype", "Address", "Size");
 | 
			
		||||
@@ -119,15 +42,170 @@ void DebugComponent::log_partition_info_() {
 | 
			
		||||
  esp_partition_iterator_release(it);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t DebugComponent::get_free_heap_() { return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); }
 | 
			
		||||
std::string DebugComponent::get_reset_reason_() {
 | 
			
		||||
  std::string reset_reason;
 | 
			
		||||
  switch (esp_reset_reason()) {
 | 
			
		||||
    case ESP_RST_POWERON:
 | 
			
		||||
      reset_reason = "Reset due to power-on event";
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_RST_EXT:
 | 
			
		||||
      reset_reason = "Reset by external pin";
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_RST_SW:
 | 
			
		||||
      reset_reason = "Software reset via esp_restart";
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_RST_PANIC:
 | 
			
		||||
      reset_reason = "Software reset due to exception/panic";
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_RST_INT_WDT:
 | 
			
		||||
      reset_reason = "Reset (software or hardware) due to interrupt watchdog";
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_RST_TASK_WDT:
 | 
			
		||||
      reset_reason = "Reset due to task watchdog";
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_RST_WDT:
 | 
			
		||||
      reset_reason = "Reset due to other watchdogs";
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_RST_DEEPSLEEP:
 | 
			
		||||
      reset_reason = "Reset after exiting deep sleep mode";
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_RST_BROWNOUT:
 | 
			
		||||
      reset_reason = "Brownout reset (software or hardware)";
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_RST_SDIO:
 | 
			
		||||
      reset_reason = "Reset over SDIO";
 | 
			
		||||
      break;
 | 
			
		||||
#ifdef USE_ESP32_VARIANT_ESP32
 | 
			
		||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4))
 | 
			
		||||
    case ESP_RST_USB:
 | 
			
		||||
      reset_reason = "Reset by USB peripheral";
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_RST_JTAG:
 | 
			
		||||
      reset_reason = "Reset by JTAG";
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_RST_EFUSE:
 | 
			
		||||
      reset_reason = "Reset due to efuse error";
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_RST_PWR_GLITCH:
 | 
			
		||||
      reset_reason = "Reset due to power glitch detected";
 | 
			
		||||
      break;
 | 
			
		||||
    case ESP_RST_CPU_LOCKUP:
 | 
			
		||||
      reset_reason = "Reset due to CPU lock up (double exception)";
 | 
			
		||||
      break;
 | 
			
		||||
#endif        // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4)
 | 
			
		||||
#endif        // USE_ESP32_VARIANT_ESP32
 | 
			
		||||
    default:  // Includes ESP_RST_UNKNOWN
 | 
			
		||||
      switch (rtc_get_reset_reason(0)) {
 | 
			
		||||
        case POWERON_RESET:
 | 
			
		||||
          reset_reason = "Power On Reset";
 | 
			
		||||
          break;
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32)
 | 
			
		||||
        case SW_RESET:
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
 | 
			
		||||
    defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
        case RTC_SW_SYS_RESET:
 | 
			
		||||
#endif
 | 
			
		||||
          reset_reason = "Software Reset Digital Core";
 | 
			
		||||
          break;
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32)
 | 
			
		||||
        case OWDT_RESET:
 | 
			
		||||
          reset_reason = "Watch Dog Reset Digital Core";
 | 
			
		||||
          break;
 | 
			
		||||
#endif
 | 
			
		||||
        case DEEPSLEEP_RESET:
 | 
			
		||||
          reset_reason = "Deep Sleep Reset Digital Core";
 | 
			
		||||
          break;
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32)
 | 
			
		||||
        case SDIO_RESET:
 | 
			
		||||
          reset_reason = "SLC Module Reset Digital Core";
 | 
			
		||||
          break;
 | 
			
		||||
#endif
 | 
			
		||||
        case TG0WDT_SYS_RESET:
 | 
			
		||||
          reset_reason = "Timer Group 0 Watch Dog Reset Digital Core";
 | 
			
		||||
          break;
 | 
			
		||||
        case TG1WDT_SYS_RESET:
 | 
			
		||||
          reset_reason = "Timer Group 1 Watch Dog Reset Digital Core";
 | 
			
		||||
          break;
 | 
			
		||||
        case RTCWDT_SYS_RESET:
 | 
			
		||||
          reset_reason = "RTC Watch Dog Reset Digital Core";
 | 
			
		||||
          break;
 | 
			
		||||
#if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
 | 
			
		||||
        case INTRUSION_RESET:
 | 
			
		||||
          reset_reason = "Intrusion Reset CPU";
 | 
			
		||||
          break;
 | 
			
		||||
#endif
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32)
 | 
			
		||||
        case TGWDT_CPU_RESET:
 | 
			
		||||
          reset_reason = "Timer Group Reset CPU";
 | 
			
		||||
          break;
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
 | 
			
		||||
    defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
        case TG0WDT_CPU_RESET:
 | 
			
		||||
          reset_reason = "Timer Group 0 Reset CPU";
 | 
			
		||||
          break;
 | 
			
		||||
#endif
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32)
 | 
			
		||||
        case SW_CPU_RESET:
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
 | 
			
		||||
    defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
        case RTC_SW_CPU_RESET:
 | 
			
		||||
#endif
 | 
			
		||||
          reset_reason = "Software Reset CPU";
 | 
			
		||||
          break;
 | 
			
		||||
        case RTCWDT_CPU_RESET:
 | 
			
		||||
          reset_reason = "RTC Watch Dog Reset CPU";
 | 
			
		||||
          break;
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32)
 | 
			
		||||
        case EXT_CPU_RESET:
 | 
			
		||||
          reset_reason = "External CPU Reset";
 | 
			
		||||
          break;
 | 
			
		||||
#endif
 | 
			
		||||
        case RTCWDT_BROWN_OUT_RESET:
 | 
			
		||||
          reset_reason = "Voltage Unstable Reset";
 | 
			
		||||
          break;
 | 
			
		||||
        case RTCWDT_RTC_RESET:
 | 
			
		||||
          reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module";
 | 
			
		||||
          break;
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
 | 
			
		||||
    defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
        case TG1WDT_CPU_RESET:
 | 
			
		||||
          reset_reason = "Timer Group 1 Reset CPU";
 | 
			
		||||
          break;
 | 
			
		||||
        case SUPER_WDT_RESET:
 | 
			
		||||
          reset_reason = "Super Watchdog Reset Digital Core And RTC Module";
 | 
			
		||||
          break;
 | 
			
		||||
        case EFUSE_RESET:
 | 
			
		||||
          reset_reason = "eFuse Reset Digital Core";
 | 
			
		||||
          break;
 | 
			
		||||
#endif
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
        case GLITCH_RTC_RESET:
 | 
			
		||||
          reset_reason = "Glitch Reset Digital Core And RTC Module";
 | 
			
		||||
          break;
 | 
			
		||||
#endif
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
        case USB_UART_CHIP_RESET:
 | 
			
		||||
          reset_reason = "USB UART Reset Digital Core";
 | 
			
		||||
          break;
 | 
			
		||||
        case USB_JTAG_CHIP_RESET:
 | 
			
		||||
          reset_reason = "USB JTAG Reset Digital Core";
 | 
			
		||||
          break;
 | 
			
		||||
#endif
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
        case POWER_GLITCH_RESET:
 | 
			
		||||
          reset_reason = "Power Glitch Reset Digital Core And RTC Module";
 | 
			
		||||
          break;
 | 
			
		||||
#endif
 | 
			
		||||
        default:
 | 
			
		||||
          reset_reason = "Unknown Reset Reason";
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
 | 
			
		||||
  return reset_reason;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const std::map<int, const char *> CHIP_FEATURES = {
 | 
			
		||||
    {CHIP_FEATURE_BLE, "BLE"},
 | 
			
		||||
    {CHIP_FEATURE_BT, "BT"},
 | 
			
		||||
    {CHIP_FEATURE_EMB_FLASH, "EMB Flash"},
 | 
			
		||||
    {CHIP_FEATURE_EMB_PSRAM, "EMB PSRAM"},
 | 
			
		||||
    {CHIP_FEATURE_WIFI_BGN, "2.4GHz WiFi"},
 | 
			
		||||
};
 | 
			
		||||
uint32_t DebugComponent::get_free_heap_() { return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); }
 | 
			
		||||
 | 
			
		||||
void DebugComponent::get_device_info_(std::string &device_info) {
 | 
			
		||||
#if defined(USE_ARDUINO)
 | 
			
		||||
@@ -164,16 +242,44 @@ void DebugComponent::get_device_info_(std::string &device_info) {
 | 
			
		||||
 | 
			
		||||
  esp_chip_info_t info;
 | 
			
		||||
  esp_chip_info(&info);
 | 
			
		||||
  const char *model = ESPHOME_VARIANT;
 | 
			
		||||
  const char *model;
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32)
 | 
			
		||||
  model = "ESP32";
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32C3)
 | 
			
		||||
  model = "ESP32-C3";
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
  model = "ESP32-C6";
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32S2)
 | 
			
		||||
  model = "ESP32-S2";
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
  model = "ESP32-S3";
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32H2)
 | 
			
		||||
  model = "ESP32-H2";
 | 
			
		||||
#else
 | 
			
		||||
  model = "UNKNOWN";
 | 
			
		||||
#endif
 | 
			
		||||
  std::string features;
 | 
			
		||||
  for (auto feature : CHIP_FEATURES) {
 | 
			
		||||
    if (info.features & feature.first) {
 | 
			
		||||
      features += feature.second;
 | 
			
		||||
      features += ", ";
 | 
			
		||||
      info.features &= ~feature.first;
 | 
			
		||||
  if (info.features & CHIP_FEATURE_EMB_FLASH) {
 | 
			
		||||
    features += "EMB_FLASH,";
 | 
			
		||||
    info.features &= ~CHIP_FEATURE_EMB_FLASH;
 | 
			
		||||
  }
 | 
			
		||||
  if (info.features & CHIP_FEATURE_WIFI_BGN) {
 | 
			
		||||
    features += "WIFI_BGN,";
 | 
			
		||||
    info.features &= ~CHIP_FEATURE_WIFI_BGN;
 | 
			
		||||
  }
 | 
			
		||||
  if (info.features != 0)
 | 
			
		||||
  if (info.features & CHIP_FEATURE_BLE) {
 | 
			
		||||
    features += "BLE,";
 | 
			
		||||
    info.features &= ~CHIP_FEATURE_BLE;
 | 
			
		||||
  }
 | 
			
		||||
  if (info.features & CHIP_FEATURE_BT) {
 | 
			
		||||
    features += "BT,";
 | 
			
		||||
    info.features &= ~CHIP_FEATURE_BT;
 | 
			
		||||
  }
 | 
			
		||||
  if (info.features & CHIP_FEATURE_EMB_PSRAM) {
 | 
			
		||||
    features += "EMB_PSRAM,";
 | 
			
		||||
    info.features &= ~CHIP_FEATURE_EMB_PSRAM;
 | 
			
		||||
  }
 | 
			
		||||
  if (info.features)
 | 
			
		||||
    features += "Other:" + format_hex(info.features);
 | 
			
		||||
  ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores,
 | 
			
		||||
           info.revision);
 | 
			
		||||
@@ -183,8 +289,6 @@ void DebugComponent::get_device_info_(std::string &device_info) {
 | 
			
		||||
  device_info += features;
 | 
			
		||||
  device_info += " Cores:" + to_string(info.cores);
 | 
			
		||||
  device_info += " Revision:" + to_string(info.revision);
 | 
			
		||||
  device_info += str_sprintf("|CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000);
 | 
			
		||||
  ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000);
 | 
			
		||||
 | 
			
		||||
  // Framework detection
 | 
			
		||||
  device_info += "|Framework: ";
 | 
			
		||||
@@ -211,7 +315,48 @@ void DebugComponent::get_device_info_(std::string &device_info) {
 | 
			
		||||
  device_info += "|Reset: ";
 | 
			
		||||
  device_info += get_reset_reason_();
 | 
			
		||||
 | 
			
		||||
  std::string wakeup_reason = this->get_wakeup_cause_();
 | 
			
		||||
  const char *wakeup_reason;
 | 
			
		||||
  switch (rtc_get_wakeup_cause()) {
 | 
			
		||||
    case NO_SLEEP:
 | 
			
		||||
      wakeup_reason = "No Sleep";
 | 
			
		||||
      break;
 | 
			
		||||
    case EXT_EVENT0_TRIG:
 | 
			
		||||
      wakeup_reason = "External Event 0";
 | 
			
		||||
      break;
 | 
			
		||||
    case EXT_EVENT1_TRIG:
 | 
			
		||||
      wakeup_reason = "External Event 1";
 | 
			
		||||
      break;
 | 
			
		||||
    case GPIO_TRIG:
 | 
			
		||||
      wakeup_reason = "GPIO";
 | 
			
		||||
      break;
 | 
			
		||||
    case TIMER_EXPIRE:
 | 
			
		||||
      wakeup_reason = "Wakeup Timer";
 | 
			
		||||
      break;
 | 
			
		||||
    case SDIO_TRIG:
 | 
			
		||||
      wakeup_reason = "SDIO";
 | 
			
		||||
      break;
 | 
			
		||||
    case MAC_TRIG:
 | 
			
		||||
      wakeup_reason = "MAC";
 | 
			
		||||
      break;
 | 
			
		||||
    case UART0_TRIG:
 | 
			
		||||
      wakeup_reason = "UART0";
 | 
			
		||||
      break;
 | 
			
		||||
    case UART1_TRIG:
 | 
			
		||||
      wakeup_reason = "UART1";
 | 
			
		||||
      break;
 | 
			
		||||
    case TOUCH_TRIG:
 | 
			
		||||
      wakeup_reason = "Touch";
 | 
			
		||||
      break;
 | 
			
		||||
    case SAR_TRIG:
 | 
			
		||||
      wakeup_reason = "SAR";
 | 
			
		||||
      break;
 | 
			
		||||
    case BT_TRIG:
 | 
			
		||||
      wakeup_reason = "BT";
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      wakeup_reason = "Unknown";
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason);
 | 
			
		||||
  device_info += "|Wakeup: ";
 | 
			
		||||
  device_info += wakeup_reason;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
from esphome.components.esp32 import CONF_CPU_FREQUENCY
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_BLOCK,
 | 
			
		||||
@@ -11,7 +10,6 @@ from esphome.const import (
 | 
			
		||||
    ICON_COUNTER,
 | 
			
		||||
    ICON_TIMER,
 | 
			
		||||
    UNIT_BYTES,
 | 
			
		||||
    UNIT_HERTZ,
 | 
			
		||||
    UNIT_MILLISECOND,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
)
 | 
			
		||||
@@ -62,14 +60,6 @@ CONFIG_SCHEMA = {
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
    ),
 | 
			
		||||
    cv.Optional(CONF_CPU_FREQUENCY): cv.All(
 | 
			
		||||
        sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_HERTZ,
 | 
			
		||||
            icon="mdi:speedometer",
 | 
			
		||||
            accuracy_decimals=0,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
    ),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -95,7 +85,3 @@ async def to_code(config):
 | 
			
		||||
    if psram_conf := config.get(CONF_PSRAM):
 | 
			
		||||
        sens = await sensor.new_sensor(psram_conf)
 | 
			
		||||
        cg.add(debug_component.set_psram_sensor(sens))
 | 
			
		||||
 | 
			
		||||
    if cpu_freq_conf := config.get(CONF_CPU_FREQUENCY):
 | 
			
		||||
        sens = await sensor.new_sensor(cpu_freq_conf)
 | 
			
		||||
        cg.add(debug_component.set_cpu_frequency_sensor(sens))
 | 
			
		||||
 
 | 
			
		||||
@@ -31,12 +31,9 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
 | 
			
		||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
 | 
			
		||||
 | 
			
		||||
#if !defined(USE_ESP32_VARIANT_ESP32H2)
 | 
			
		||||
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) {
 | 
			
		||||
  wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration;
 | 
			
		||||
}
 | 
			
		||||
@@ -68,7 +65,7 @@ bool DeepSleepComponent::prepare_to_sleep_() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DeepSleepComponent::deep_sleep_() {
 | 
			
		||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
 | 
			
		||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
  if (this->sleep_duration_.has_value())
 | 
			
		||||
    esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
 | 
			
		||||
  if (this->wakeup_pin_ != nullptr) {
 | 
			
		||||
@@ -87,15 +84,6 @@ void DeepSleepComponent::deep_sleep_() {
 | 
			
		||||
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32H2)
 | 
			
		||||
  if (this->sleep_duration_.has_value())
 | 
			
		||||
    esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
 | 
			
		||||
  if (this->ext1_wakeup_.has_value()) {
 | 
			
		||||
    esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
  if (this->sleep_duration_.has_value())
 | 
			
		||||
    esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,20 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import climate_ir
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["climate_ir"]
 | 
			
		||||
 | 
			
		||||
delonghi_ns = cg.esphome_ns.namespace("delonghi")
 | 
			
		||||
DelonghiClimate = delonghi_ns.class_("DelonghiClimate", climate_ir.ClimateIR)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DelonghiClimate)
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(DelonghiClimate),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    await climate_ir.new_climate_ir(config)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await climate_ir.register_climate_ir(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_DEVICE_CLASS,
 | 
			
		||||
    CONF_FORCE_UPDATE,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INVERTED,
 | 
			
		||||
    CONF_MAX_VALUE,
 | 
			
		||||
    CONF_MIN_VALUE,
 | 
			
		||||
@@ -152,10 +153,9 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        ): [
 | 
			
		||||
            climate.climate_schema(DemoClimate)
 | 
			
		||||
            .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
            .extend(
 | 
			
		||||
            climate.CLIMATE_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(): cv.declare_id(DemoClimate),
 | 
			
		||||
                    cv.Required(CONF_TYPE): cv.enum(CLIMATE_TYPES, int=True),
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
@@ -183,10 +183,9 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        ): [
 | 
			
		||||
            cover.cover_schema(DemoCover)
 | 
			
		||||
            .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
            .extend(
 | 
			
		||||
            cover.COVER_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(): cv.declare_id(DemoCover),
 | 
			
		||||
                    cv.Required(CONF_TYPE): cv.enum(COVER_TYPES, int=True),
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
@@ -212,10 +211,9 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        ): [
 | 
			
		||||
            fan.fan_schema(DemoFan)
 | 
			
		||||
            .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
            .extend(
 | 
			
		||||
            fan.FAN_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(DemoFan),
 | 
			
		||||
                    cv.Required(CONF_TYPE): cv.enum(FAN_TYPES, int=True),
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
@@ -253,9 +251,7 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        ): [
 | 
			
		||||
            light.light_schema(DemoLight, light.LightType.RGB)
 | 
			
		||||
            .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
            .extend(
 | 
			
		||||
            light.RGB_LIGHT_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(DemoLight),
 | 
			
		||||
                    cv.Required(CONF_TYPE): cv.enum(LIGHT_TYPES, int=True),
 | 
			
		||||
@@ -381,33 +377,39 @@ async def to_code(config):
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config[CONF_CLIMATES]:
 | 
			
		||||
        var = await climate.new_climate(conf)
 | 
			
		||||
        var = cg.new_Pvariable(conf[CONF_ID])
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
        await climate.register_climate(var, conf)
 | 
			
		||||
        cg.add(var.set_type(conf[CONF_TYPE]))
 | 
			
		||||
 | 
			
		||||
    for conf in config[CONF_COVERS]:
 | 
			
		||||
        var = await cover.new_cover(conf)
 | 
			
		||||
        var = cg.new_Pvariable(conf[CONF_ID])
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
        await cover.register_cover(var, conf)
 | 
			
		||||
        cg.add(var.set_type(conf[CONF_TYPE]))
 | 
			
		||||
 | 
			
		||||
    for conf in config[CONF_FANS]:
 | 
			
		||||
        var = await fan.new_fan(conf)
 | 
			
		||||
        var = cg.new_Pvariable(conf[CONF_OUTPUT_ID])
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
        await fan.register_fan(var, conf)
 | 
			
		||||
        cg.add(var.set_type(conf[CONF_TYPE]))
 | 
			
		||||
 | 
			
		||||
    for conf in config[CONF_LIGHTS]:
 | 
			
		||||
        var = await light.new_light(conf)
 | 
			
		||||
        var = cg.new_Pvariable(conf[CONF_OUTPUT_ID])
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
        await light.register_light(var, conf)
 | 
			
		||||
        cg.add(var.set_type(conf[CONF_TYPE]))
 | 
			
		||||
 | 
			
		||||
    for conf in config[CONF_NUMBERS]:
 | 
			
		||||
        var = await number.new_number(
 | 
			
		||||
        var = cg.new_Pvariable(conf[CONF_ID])
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
        await number.register_number(
 | 
			
		||||
            var,
 | 
			
		||||
            conf,
 | 
			
		||||
            min_value=conf[CONF_MIN_VALUE],
 | 
			
		||||
            max_value=conf[CONF_MAX_VALUE],
 | 
			
		||||
            step=conf[CONF_STEP],
 | 
			
		||||
        )
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
        cg.add(var.set_type(conf[CONF_TYPE]))
 | 
			
		||||
 | 
			
		||||
    for conf in config[CONF_SENSORS]:
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ import esphome.codegen as cg
 | 
			
		||||
from esphome.components import switch
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_TYPE, ENTITY_CATEGORY_CONFIG
 | 
			
		||||
from esphome.cpp_generator import MockObjClass
 | 
			
		||||
 | 
			
		||||
from .. import CONF_DFROBOT_SEN0395_ID, DfrobotSen0395Component
 | 
			
		||||
 | 
			
		||||
@@ -27,30 +26,32 @@ Sen0395StartAfterBootSwitch = dfrobot_sen0395_ns.class_(
 | 
			
		||||
    "Sen0395StartAfterBootSwitch", DfrobotSen0395Switch
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _switch_schema(class_: MockObjClass) -> cv.Schema:
 | 
			
		||||
    return (
 | 
			
		||||
_SWITCH_SCHEMA = (
 | 
			
		||||
    switch.switch_schema(
 | 
			
		||||
            class_,
 | 
			
		||||
        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
    )
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
                cv.GenerateID(CONF_DFROBOT_SEN0395_ID): cv.use_id(
 | 
			
		||||
                    DfrobotSen0395Component
 | 
			
		||||
                ),
 | 
			
		||||
            cv.GenerateID(CONF_DFROBOT_SEN0395_ID): cv.use_id(DfrobotSen0395Component),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.typed_schema(
 | 
			
		||||
    {
 | 
			
		||||
        "sensor_active": _switch_schema(Sen0395PowerSwitch),
 | 
			
		||||
        "turn_on_led": _switch_schema(Sen0395LedSwitch),
 | 
			
		||||
        "presence_via_uart": _switch_schema(Sen0395UartPresenceSwitch),
 | 
			
		||||
        "start_after_boot": _switch_schema(Sen0395StartAfterBootSwitch),
 | 
			
		||||
        "sensor_active": _SWITCH_SCHEMA.extend(
 | 
			
		||||
            {cv.GenerateID(): cv.declare_id(Sen0395PowerSwitch)}
 | 
			
		||||
        ),
 | 
			
		||||
        "turn_on_led": _SWITCH_SCHEMA.extend(
 | 
			
		||||
            {cv.GenerateID(): cv.declare_id(Sen0395LedSwitch)}
 | 
			
		||||
        ),
 | 
			
		||||
        "presence_via_uart": _SWITCH_SCHEMA.extend(
 | 
			
		||||
            {cv.GenerateID(): cv.declare_id(Sen0395UartPresenceSwitch)}
 | 
			
		||||
        ),
 | 
			
		||||
        "start_after_boot": _SWITCH_SCHEMA.extend(
 | 
			
		||||
            {cv.GenerateID(): cv.declare_id(Sen0395StartAfterBootSwitch)}
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user