diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7abcb43417..4596b59200 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,8 +7,21 @@ "PIP_BREAK_SYSTEM_PACKAGES": "1", "PIP_ROOT_USER_ACTION": "ignore" }, - "runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"], + "runArgs": [ + "--privileged", + "-e", + "ESPHOME_DASHBOARD_USE_PING=1" + // uncomment and edit the path in order to pass though local USB serial to the conatiner + // , "--device=/dev/ttyACM0" + ], "appPort": 6052, + // if you are using avahi in the host device, uncomment these to allow the + // devcontainer to find devices via mdns + //"mounts": [ + // "type=bind,source=/dev/bus/usb,target=/dev/bus/usb", + // "type=bind,source=/var/run/dbus,target=/var/run/dbus", + // "type=bind,source=/var/run/avahi-daemon/socket,target=/var/run/avahi-daemon/socket" + //], "customizations": { "vscode": { "extensions": [ diff --git a/.github/actions/build-image/action.yaml b/.github/actions/build-image/action.yaml index 4c98a47ecd..838bc362a1 100644 --- a/.github/actions/build-image/action.yaml +++ b/.github/actions/build-image/action.yaml @@ -36,7 +36,7 @@ runs: - name: Build and push to ghcr by digest id: build-ghcr - uses: docker/build-push-action@v5.0.0 + uses: docker/build-push-action@v5.3.0 with: context: . file: ./docker/Dockerfile @@ -67,7 +67,7 @@ runs: - name: Build and push to dockerhub by digest id: build-dockerhub - uses: docker/build-push-action@v5.0.0 + uses: docker/build-push-action@v5.3.0 with: context: . file: ./docker/Dockerfile diff --git a/.github/actions/restore-python/action.yml b/.github/actions/restore-python/action.yml index 18a2485dbb..4ad9e2eaed 100644 --- a/.github/actions/restore-python/action.yml +++ b/.github/actions/restore-python/action.yml @@ -17,22 +17,31 @@ runs: steps: - name: Set up Python ${{ inputs.python-version }} id: python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ inputs.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache/restore@v3.3.2 + uses: actions/cache/restore@v4.0.2 with: path: venv # yamllint disable-line rule:line-length key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ inputs.cache-key }} - name: Create Python virtual environment - if: steps.cache-venv.outputs.cache-hit != 'true' + if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os != 'Windows' shell: bash run: | python -m venv venv - . venv/bin/activate + source venv/bin/activate + python --version + pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt + pip install -e . + - name: Create Python virtual environment + if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os == 'Windows' + shell: bash + run: | + python -m venv venv + ./venv/Scripts/activate python --version pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt pip install -e . diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 666532f360..3b6495653b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,3 +13,13 @@ updates: schedule: interval: daily open-pull-requests-limit: 10 + - package-ecosystem: github-actions + directory: "/.github/actions/build-image" + schedule: + interval: daily + open-pull-requests-limit: 10 + - package-ecosystem: github-actions + directory: "/.github/actions/restore-python" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/ci-api-proto.yml b/.github/workflows/ci-api-proto.yml new file mode 100644 index 0000000000..40766ad728 --- /dev/null +++ b/.github/workflows/ci-api-proto.yml @@ -0,0 +1,80 @@ +name: API Proto CI + +on: + pull_request: + paths: + - "esphome/components/api/api.proto" + - "esphome/components/api/api_pb2.cpp" + - "esphome/components/api/api_pb2.h" + - "esphome/components/api/api_pb2_service.cpp" + - "esphome/components/api/api_pb2_service.h" + - "script/api_protobuf/api_protobuf.py" + - ".github/workflows/ci-api-proto.yml" + +permissions: + contents: read + pull-requests: write + +jobs: + check: + name: Check generated files + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + - name: Set up Python + uses: actions/setup-python@v5.1.0 + with: + python-version: "3.11" + + - name: Install apt dependencies + run: | + sudo apt update + sudo apt-cache show protobuf-compiler + sudo apt install -y protobuf-compiler + protoc --version + - name: Install python dependencies + run: pip install aioesphomeapi -c requirements.txt -r requirements_dev.txt + - name: Generate files + run: script/api_protobuf/api_protobuf.py + - name: Check for changes + run: | + if ! git diff --quiet; then + echo "## Job Failed" | tee -a $GITHUB_STEP_SUMMARY + echo "You have altered the generated proto files but they do not match what is expected." | tee -a $GITHUB_STEP_SUMMARY + echo "Please run 'script/api_protobuf/api_protobuf.py' and commit the changes." | tee -a $GITHUB_STEP_SUMMARY + exit 1 + fi + - if: failure() + name: Review PR + uses: actions/github-script@v7.0.1 + with: + script: | + await github.rest.pulls.createReview({ + pull_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + 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: success() + name: Dismiss review + uses: actions/github-script@v7.0.1 + with: + script: | + let reviews = await github.rest.pulls.listReviews({ + pull_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo + }); + for (let review of reviews.data) { + if (review.user.login === 'github-actions[bot]' && review.state === 'CHANGES_REQUESTED') { + await github.rest.pulls.dismissReview({ + pull_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + review_id: review.id, + message: 'Files now match the expected proto files.' + }); + } + } diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 8fe8bbdc52..d8fd4efa0e 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -2,7 +2,7 @@ name: CI for docker images # Only run when docker paths change -# yamllint disable-line rule:truthy + on: push: branches: [dev, beta, release] @@ -42,11 +42,11 @@ jobs: steps: - uses: actions/checkout@v4.1.1 - name: Set up Python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: "3.9" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.0.0 + uses: docker/setup-buildx-action@v3.2.0 - name: Set up QEMU uses: docker/setup-qemu-action@v3.0.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b33321ae6..2fdd3e7b3e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,6 @@ --- name: CI -# yamllint disable-line rule:truthy on: push: branches: [dev, beta, release] @@ -11,6 +10,8 @@ on: - "**" - "!.github/workflows/*.yml" - ".github/workflows/ci.yml" + - "!.yamllint" + - "!.github/dependabot.yml" merge_group: permissions: @@ -40,12 +41,12 @@ jobs: run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.2 with: path: venv # yamllint disable-line rule:line-length @@ -166,7 +167,35 @@ jobs: pytest: name: Run pytest - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.9" + - "3.10" + - "3.11" + - "3.12" + os: + - ubuntu-latest + - macOS-latest + - windows-latest + exclude: + # Minimize CI resource usage + # by only running the Python version + # version used for docker images on Windows and macOS + - python-version: "3.12" + os: windows-latest + - python-version: "3.10" + os: windows-latest + - python-version: "3.9" + os: windows-latest + - python-version: "3.12" + os: macOS-latest + - python-version: "3.10" + os: macOS-latest + - python-version: "3.9" + os: macOS-latest + runs-on: ${{ matrix.os }} needs: - common steps: @@ -175,14 +204,24 @@ jobs: - name: Restore Python uses: ./.github/actions/restore-python with: - python-version: ${{ env.DEFAULT_PYTHON }} + python-version: ${{ matrix.python-version }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Register matcher run: echo "::add-matcher::.github/workflows/matchers/pytest.json" - name: Run pytest + if: matrix.os == 'windows-latest' + run: | + ./venv/Scripts/activate + pytest -vv --cov-report=xml --tb=native tests + - name: Run pytest + if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest' run: | . venv/bin/activate - pytest -vv --tb=native tests + pytest -vv --cov-report=xml --tb=native tests + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} clang-format: name: Check clang-format @@ -331,7 +370,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Cache platformio - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.2 with: path: ~/.platformio # yamllint disable-line rule:line-length @@ -358,6 +397,70 @@ jobs: # yamllint disable-line rule:line-length if: always() + list-components: + runs-on: ubuntu-latest + needs: + - common + if: github.event_name == 'pull_request' + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Check out code from GitHub + uses: actions/checkout@v4.1.1 + with: + # Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works. + fetch-depth: 500 + - name: Get target branch + id: target-branch + run: | + echo "branch=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT + - name: Fetch ${{ steps.target-branch.outputs.branch }} branch + run: | + git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/${{ steps.target-branch.outputs.branch }}:refs/remotes/origin/${{ steps.target-branch.outputs.branch }} + git merge-base refs/remotes/origin/${{ steps.target-branch.outputs.branch }} HEAD + - name: Restore Python + uses: ./.github/actions/restore-python + with: + python-version: ${{ env.DEFAULT_PYTHON }} + cache-key: ${{ needs.common.outputs.cache-key }} + - name: Find changed components + id: set-matrix + run: | + . venv/bin/activate + echo "matrix=$(script/list-components.py --changed --branch ${{ steps.target-branch.outputs.branch }} | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT + + test-build-components: + name: Component test ${{ matrix.file }} + runs-on: ubuntu-latest + needs: + - common + - list-components + if: ${{ github.event_name == 'pull_request' && needs.list-components.outputs.matrix != '[]' && needs.list-components.outputs.matrix != '' }} + strategy: + fail-fast: false + max-parallel: 2 + matrix: + file: ${{ fromJson(needs.list-components.outputs.matrix) }} + steps: + - name: Install libsodium + run: sudo apt-get install libsodium-dev + + - name: Check out code from GitHub + uses: actions/checkout@v4.1.1 + - name: Restore Python + uses: ./.github/actions/restore-python + with: + python-version: ${{ env.DEFAULT_PYTHON }} + cache-key: ${{ needs.common.outputs.cache-key }} + - name: test_build_components -e config -c ${{ matrix.file }} + run: | + . venv/bin/activate + ./script/test_build_components -e config -c ${{ matrix.file }} + - name: test_build_components -e compile -c ${{ matrix.file }} + run: | + . venv/bin/activate + ./script/test_build_components -e compile -c ${{ matrix.file }} + ci-status: name: CI Status runs-on: ubuntu-latest @@ -372,6 +475,7 @@ jobs: - pyupgrade - compile-tests - clang-tidy + - test-build-components if: always() steps: - name: Success diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index e3d75f6d58..ee10f49f61 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -1,7 +1,6 @@ --- name: Lock -# yamllint disable-line rule:truthy on: schedule: - cron: "30 0 * * *" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 625a8c8ecb..cb7defc2b0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,6 @@ --- name: Publish Release -# yamllint disable-line rule:truthy on: workflow_dispatch: release: @@ -45,7 +44,7 @@ jobs: steps: - uses: actions/checkout@v4.1.1 - name: Set up Python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: "3.x" - name: Set up python environment @@ -80,23 +79,23 @@ jobs: steps: - uses: actions/checkout@v4.1.1 - name: Set up Python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: "3.9" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.0.0 + uses: docker/setup-buildx-action@v3.2.0 - name: Set up QEMU if: matrix.platform != 'linux/amd64' uses: docker/setup-qemu-action@v3.0.0 - name: Log in to docker hub - uses: docker/login-action@v3.0.0 + uses: docker/login-action@v3.1.0 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to the GitHub container registry - uses: docker/login-action@v3.0.0 + uses: docker/login-action@v3.1.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -163,17 +162,17 @@ jobs: name: digests-${{ matrix.image.target }}-${{ matrix.registry }} path: /tmp/digests - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.0.0 + uses: docker/setup-buildx-action@v3.2.0 - name: Log in to docker hub if: matrix.registry == 'dockerhub' - uses: docker/login-action@v3.0.0 + uses: docker/login-action@v3.1.0 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to the GitHub container registry if: matrix.registry == 'ghcr' - uses: docker/login-action@v3.0.0 + uses: docker/login-action@v3.1.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 5f510ffe75..95f275e5a4 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,7 +1,6 @@ --- name: Stale -# yamllint disable-line rule:truthy on: schedule: - cron: "30 0 * * *" diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index d45784bf7f..c12f1f31b5 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -22,7 +22,7 @@ jobs: path: lib/home-assistant - name: Setup Python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: 3.11 @@ -36,7 +36,7 @@ jobs: python ./script/sync-device_class.py - name: Commit changes - uses: peter-evans/create-pull-request@v5.0.2 + uses: peter-evans/create-pull-request@v6.0.2 with: commit-message: "Synchronise Device Classes from Home Assistant" committer: esphomebot diff --git a/.github/workflows/yaml-lint.yml b/.github/workflows/yaml-lint.yml index c9f056b18c..a3c1937e56 100644 --- a/.github/workflows/yaml-lint.yml +++ b/.github/workflows/yaml-lint.yml @@ -1,3 +1,4 @@ +--- name: YAML lint on: @@ -19,4 +20,6 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v4.1.1 - name: Run yamllint - uses: frenck/action-yamllint@v1.4.2 + uses: frenck/action-yamllint@v1.5.0 + with: + strict: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 36ec1894d8..7865c52abd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.12.0 + rev: 24.2.0 hooks: - id: black args: @@ -27,7 +27,7 @@ repos: - --branch=release - --branch=beta - repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 + rev: v3.15.1 hooks: - id: pyupgrade args: [--py39-plus] diff --git a/.yamllint b/.yamllint index 4fea263214..22e9237f61 100644 --- a/.yamllint +++ b/.yamllint @@ -1,3 +1,19 @@ --- -ignore: | - venv/ +extends: default + +ignore-from-file: .gitignore + +rules: + document-start: disable + empty-lines: + level: error + max: 1 + max-start: 0 + max-end: 1 + indentation: + level: error + spaces: 2 + indent-sequences: true + check-multi-line-strings: false + line-length: disable + truthy: disable diff --git a/CODEOWNERS b/CODEOWNERS index 320a23ffaa..fafbaae6c6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -18,15 +18,19 @@ esphome/components/ac_dimmer/* @glmnet esphome/components/adc/* @esphome/core esphome/components/adc128s102/* @DeerMaximum esphome/components/addressable_light/* @justfalter +esphome/components/ade7880/* @kpfleming esphome/components/ade7953/* @angelnu esphome/components/ade7953_i2c/* @angelnu esphome/components/ade7953_spi/* @angelnu +esphome/components/ads1118/* @solomondg1 +esphome/components/ags10/* @mak-42 esphome/components/airthings_ble/* @jeromelaban esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_plus/* @jeromelaban -esphome/components/alarm_control_panel/* @grahambrown11 +esphome/components/alarm_control_panel/* @grahambrown11 @hwstar esphome/components/alpha3/* @jan-hofmeier +esphome/components/am2315c/* @swoboda1337 esphome/components/am43/* @buxtronix esphome/components/am43/cover/* @buxtronix esphome/components/am43/sensor/* @buxtronix @@ -34,8 +38,11 @@ esphome/components/analog_threshold/* @ianchi esphome/components/animation/* @syndlex esphome/components/anova/* @buxtronix esphome/components/api/* @OttoWinter +esphome/components/as5600/* @ammmze +esphome/components/as5600/sensor/* @ammmze esphome/components/as7341/* @mrgnr esphome/components/async_tcp/* @OttoWinter +esphome/components/at581x/* @X-Ryl669 esphome/components/atc_mithermometer/* @ahpohl esphome/components/atm90e26/* @danieltwagner esphome/components/b_parasite/* @rbaron @@ -50,8 +57,10 @@ esphome/components/bk72xx/* @kuba2k2 esphome/components/bl0939/* @ziceva esphome/components/bl0940/* @tobias- esphome/components/bl0942/* @dbuezas -esphome/components/ble_client/* @buxtronix +esphome/components/ble_client/* @buxtronix @clydebarrow esphome/components/bluetooth_proxy/* @jesserockz +esphome/components/bme280_base/* @esphome/core +esphome/components/bme280_spi/* @apbodrov esphome/components/bme680_bsec/* @trvrnrth esphome/components/bmi160/* @flaviut esphome/components/bmp3xx/* @martgras @@ -67,17 +76,22 @@ esphome/components/cd74hc4067/* @asoehlke esphome/components/climate/* @esphome/core esphome/components/climate_ir/* @glmnet esphome/components/color_temperature/* @jesserockz +esphome/components/combination/* @Cat-Ion @kahrendt esphome/components/coolix/* @glmnet esphome/components/copy/* @OttoWinter esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun esphome/components/cse7761/* @berfenger +esphome/components/cst226/* @clydebarrow +esphome/components/cst816/* @clydebarrow esphome/components/ct_clamp/* @jesserockz esphome/components/current_based/* @djwmarcx esphome/components/dac7678/* @NickB1 +esphome/components/daikin_arc/* @MagicBear esphome/components/daikin_brc/* @hagak esphome/components/daly_bms/* @s1lvi0 esphome/components/dashboard_import/* @esphome/core +esphome/components/datetime/* @rfdarter esphome/components/debug/* @OttoWinter esphome/components/delonghi/* @grob6000 esphome/components/dfplayer/* @glmnet @@ -91,6 +105,7 @@ esphome/components/duty_time/* @dudanov esphome/components/ee895/* @Stock-M esphome/components/ektf2232/touchscreen/* @jesserockz esphome/components/emc2101/* @ellull +esphome/components/emmeti/* @E440QF esphome/components/ens160/* @vincentscode esphome/components/ens210/* @itn3rd77 esphome/components/esp32/* @esphome/core @@ -109,7 +124,8 @@ esphome/components/ezo_pmp/* @carlos-sarmiento esphome/components/factory_reset/* @anatoly-savchenkov esphome/components/fastled_base/* @OttoWinter esphome/components/feedback/* @ianchi -esphome/components/fingerprint_grow/* @OnFreund @loongyh +esphome/components/fingerprint_grow/* @OnFreund @alexborro @loongyh +esphome/components/font/* @clydebarrow @esphome/core esphome/components/fs3000/* @kahrendt esphome/components/ft5x06/* @clydebarrow esphome/components/ft63x6/* @gpambrozio @@ -133,11 +149,13 @@ esphome/components/heatpumpir/* @rob-deutsch esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/hm3301/* @freekode esphome/components/homeassistant/* @OttoWinter +esphome/components/honeywell_hih_i2c/* @Benichou34 esphome/components/honeywellabp/* @RubyBailey esphome/components/honeywellabp2_i2c/* @jpfaff esphome/components/host/* @esphome/core esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hte501/* @Stock-M +esphome/components/htu31d/* @betterengineering esphome/components/hydreon_rgxx/* @functionpointer esphome/components/hyt271/* @Philippe12 esphome/components/i2c/* @esphome/core @@ -149,14 +167,16 @@ esphome/components/iaqcore/* @yozik04 esphome/components/ili9xxx/* @clydebarrow @nielsnl68 esphome/components/improv_base/* @esphome/core esphome/components/improv_serial/* @esphome/core +esphome/components/ina226/* @Sergio303 @latonita esphome/components/ina260/* @mreditor97 esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkplate6/* @jesserockz esphome/components/integration/* @OttoWinter esphome/components/internal_temperature/* @Mat931 esphome/components/interval/* @esphome/core +esphome/components/jsn_sr04t/* @Mafus1 esphome/components/json/* @OttoWinter -esphome/components/kalman_combinator/* @Cat-Ion +esphome/components/kamstrup_kmp/* @cfeenstra1024 esphome/components/key_collector/* @ssieb esphome/components/key_provider/* @ssieb esphome/components/kuntze/* @ssieb @@ -194,6 +214,7 @@ esphome/components/mcp9808/* @k7hpn esphome/components/md5/* @esphome/core 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 esphome/components/mics_4514/* @jesserockz @@ -217,13 +238,14 @@ esphome/components/mopeka_pro_check/* @spbrogan esphome/components/mopeka_std_check/* @Fabian-Schmidt esphome/components/mpl3115a2/* @kbickar esphome/components/mpu6886/* @fabaff +esphome/components/ms8607/* @e28eta esphome/components/network/* @esphome/core esphome/components/nextion/* @senexcrenshaw esphome/components/nextion/binary_sensor/* @senexcrenshaw esphome/components/nextion/sensor/* @senexcrenshaw esphome/components/nextion/switch/* @senexcrenshaw esphome/components/nextion/text_sensor/* @senexcrenshaw -esphome/components/nfc/* @jesserockz +esphome/components/nfc/* @jesserockz @kbx81 esphome/components/noblex/* @AGalfra esphome/components/number/* @esphome/core esphome/components/ota/* @esphome/core @@ -253,6 +275,7 @@ esphome/components/pvvx_mithermometer/* @pasiz esphome/components/pylontech/* @functionpointer esphome/components/qmp6988/* @andrewpc esphome/components/qr_code/* @wjtje +esphome/components/qspi_amoled/* @clydebarrow esphome/components/qwiic_pir/* @kahrendt esphome/components/radon_eye_ble/* @jeffeb3 esphome/components/radon_eye_rd200/* @jeffeb3 @@ -266,6 +289,7 @@ esphome/components/rgbct/* @jesserockz esphome/components/rp2040/* @jesserockz esphome/components/rp2040_pio_led_strip/* @Papa-DMan esphome/components/rp2040_pwm/* @jesserockz +esphome/components/rpi_dpi_rgb/* @clydebarrow esphome/components/rtl87xx/* @kuba2k2 esphome/components/rtttl/* @glmnet esphome/components/safe_mode/* @jsuanet @paulmonigatti @@ -273,6 +297,7 @@ esphome/components/scd4x/* @martgras @sjtrny esphome/components/script/* @esphome/core esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath +esphome/components/seeed_mr24hpc1/* @limengdu esphome/components/selec_meter/* @sourabhjaiswal esphome/components/select/* @esphome/core esphome/components/sen0321/* @notjj @@ -284,6 +309,7 @@ esphome/components/sfa30/* @ghsensdev esphome/components/sgp40/* @SenexCrenshaw esphome/components/sgp4x/* @SenexCrenshaw @martgras esphome/components/shelly_dimmer/* @edge90 @rnauber +esphome/components/sht3xd/* @mrtoy-me esphome/components/sht4x/* @sjtrny esphome/components/shutdown/* @esphome/core @jsuanet esphome/components/sigma_delta_output/* @Cat-Ion @@ -314,18 +340,25 @@ esphome/components/ssd1331_base/* @kbx81 esphome/components/ssd1331_spi/* @kbx81 esphome/components/ssd1351_base/* @kbx81 esphome/components/ssd1351_spi/* @kbx81 +esphome/components/st7567_base/* @latonita +esphome/components/st7567_i2c/* @latonita +esphome/components/st7567_spi/* @latonita +esphome/components/st7701s/* @clydebarrow esphome/components/st7735/* @SenexCrenshaw esphome/components/st7789v/* @kbx81 esphome/components/st7920/* @marsjan155 esphome/components/substitutions/* @esphome/core esphome/components/sun/* @OttoWinter +esphome/components/sun_gtil2/* @Mat931 esphome/components/switch/* @esphome/core esphome/components/t6615/* @tylermenezes esphome/components/tca9548a/* @andreashergert1984 esphome/components/tcl112/* @glmnet esphome/components/tee501/* @Stock-M esphome/components/teleinfo/* @0hax -esphome/components/template/alarm_control_panel/* @grahambrown11 +esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar +esphome/components/template/datetime/* @rfdarter +esphome/components/template/fan/* @ssieb esphome/components/text/* @mauritskorse esphome/components/thermostat/* @kbx81 esphome/components/time/* @OttoWinter @@ -354,10 +387,14 @@ esphome/components/uart/button/* @ssieb esphome/components/ufire_ec/* @pvizeli esphome/components/ufire_ise/* @pvizeli esphome/components/ultrasonic/* @OttoWinter +esphome/components/uponor_smatrix/* @kroimon esphome/components/vbus/* @ssieb +esphome/components/veml3235/* @kbx81 +esphome/components/veml7700/* @latonita esphome/components/version/* @esphome/core esphome/components/voice_assistant/* @jesserockz esphome/components/wake_on_lan/* @willwill2will54 +esphome/components/waveshare_epaper/* @clydebarrow esphome/components/web_server_base/* @OttoWinter esphome/components/web_server_idf/* @dentra esphome/components/whirlpool/* @glmnet diff --git a/docker/Dockerfile b/docker/Dockerfile index ee7c70bb0f..5d9ece16a1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -34,8 +34,8 @@ RUN \ python3-wheel=0.38.4-2 \ iputils-ping=3:20221126-1 \ git=1:2.39.2-1.1 \ - curl=7.88.1-10+deb12u4 \ - openssh-client=1:9.2p1-2+deb12u1 \ + curl=7.88.1-10+deb12u5 \ + openssh-client=1:9.2p1-2+deb12u2 \ python3-cffi=1.15.1-5 \ libcairo2=1.16.0-7 \ libmagic1=1:5.44-3 \ @@ -50,7 +50,7 @@ RUN \ libssl-dev=3.0.11-1~deb12u2 \ libffi-dev=3.4.4-1 \ libopenjp2-7=2.5.0-2 \ - libtiff6=4.5.0-6 \ + libtiff6=4.5.0-6+deb12u1 \ cargo=0.66.0+ds1-1 \ pkg-config=1.8.1-1 \ gcc-arm-linux-gnueabihf=4:12.2.0-3; \ @@ -81,7 +81,7 @@ RUN \ fi; \ pip3 install \ --break-system-packages --no-cache-dir \ - platformio==6.1.11 \ + platformio==6.1.13 \ # Change some platformio settings && platformio settings set enable_telemetry No \ && platformio settings set check_platformio_interval 1000000 \ diff --git a/docker/docker_entrypoint.sh b/docker/docker_entrypoint.sh index 75d5e0b7b5..397b1528c5 100755 --- a/docker/docker_entrypoint.sh +++ b/docker/docker_entrypoint.sh @@ -21,4 +21,10 @@ export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms" export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages" export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache" +# If /build is mounted, use that as the build path +# otherwise use path in /config (so that builds aren't lost on container restart) +if [[ -d /build ]]; then + export ESPHOME_BUILD_PATH=/build +fi + exec esphome "$@" diff --git a/esphome/__main__.py b/esphome/__main__.py index 0796dead43..95d444ca9b 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -12,7 +12,7 @@ import argcomplete from esphome import const, writer, yaml_util import esphome.codegen as cg -from esphome.config import iter_components, read_config, strip_default_ids +from esphome.config import iter_component_configs, read_config, strip_default_ids from esphome.const import ( ALLOWED_NAME_CHARS, CONF_BAUD_RATE, @@ -196,7 +196,7 @@ def write_cpp(config): def generate_cpp_contents(config): _LOGGER.info("Generating C++ source...") - for name, component, conf in iter_components(CORE.config): + for name, component, conf in iter_component_configs(CORE.config): if component.to_code is not None: coro = wrap_to_code(name, component) CORE.add_job(coro, conf) @@ -297,8 +297,27 @@ def upload_using_platformio(config, port): return platformio_api.run_platformio_cli_run(config, CORE.verbose, *upload_args) +def check_permissions(port): + if os.name == "posix" and get_port_type(port) == "SERIAL": + # Check if we can open selected serial port + if not os.access(port, os.F_OK): + raise EsphomeError( + "The selected serial port does not exist. To resolve this issue, " + "check that the device is connected to this computer with a USB cable and that " + "the USB cable can be used for data and is not a power-only cable." + ) + if not (os.access(port, os.R_OK | os.W_OK)): + raise EsphomeError( + "You do not have read or write permission on the selected serial port. " + "To resolve this issue, you can add your user to the dialout group " + f"by running the following command: sudo usermod -a -G dialout {os.getlogin()}. " + "You will need to log out & back in or reboot to activate the new group access." + ) + + def upload_program(config, args, host): if get_port_type(host) == "SERIAL": + check_permissions(host) if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266): file = getattr(args, "file", None) return upload_using_esptool(config, host, file) @@ -344,6 +363,7 @@ def show_logs(config, args, port): if "logger" not in config: raise EsphomeError("Logger is not configured!") if get_port_type(port) == "SERIAL": + check_permissions(port) return run_miniterm(config, port) if get_port_type(port) == "NETWORK" and "api" in config: if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config: diff --git a/esphome/codegen.py b/esphome/codegen.py index 43b44256e2..dc17f28a03 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -87,4 +87,5 @@ from esphome.cpp_types import ( # noqa gpio_Flags, EntityCategory, Parented, + ESPTime, ) diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index 952fbdd9b9..87d769fec2 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -139,6 +139,9 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = { VARIANT_ESP32C3: { 5: adc2_channel_t.ADC2_CHANNEL_0, }, + VARIANT_ESP32C2: {}, + VARIANT_ESP32C6: {}, + VARIANT_ESP32H2: {}, } diff --git a/esphome/components/ade7880/__init__.py b/esphome/components/ade7880/__init__.py new file mode 100644 index 0000000000..aed63c7dfa --- /dev/null +++ b/esphome/components/ade7880/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@kpfleming"] diff --git a/esphome/components/ade7880/ade7880.cpp b/esphome/components/ade7880/ade7880.cpp new file mode 100644 index 0000000000..31b72d51a6 --- /dev/null +++ b/esphome/components/ade7880/ade7880.cpp @@ -0,0 +1,302 @@ +// This component was developed using knowledge gathered by a number +// of people who reverse-engineered the Shelly 3EM: +// +// @AndreKR on GitHub +// Axel (@Axel830 on GitHub) +// Marko (@goodkiller on GitHub) +// Michaël Piron (@michaelpiron on GitHub) +// Theo Arends (@arendst on GitHub) + +#include "ade7880.h" +#include "ade7880_registers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ade7880 { + +static const char *const TAG = "ade7880"; + +void IRAM_ATTR ADE7880Store::gpio_intr(ADE7880Store *arg) { arg->reset_done = true; } + +void ADE7880::setup() { + if (this->irq0_pin_ != nullptr) { + this->irq0_pin_->setup(); + } + this->irq1_pin_->setup(); + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + } + this->store_.irq1_pin = this->irq1_pin_->to_isr(); + this->irq1_pin_->attach_interrupt(ADE7880Store::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE); + + // if IRQ1 is already asserted, the cause must be determined + if (this->irq1_pin_->digital_read() == 0) { + ESP_LOGD(TAG, "IRQ1 found asserted during setup()"); + auto status1 = read_u32_register16_(STATUS1); + if ((status1 & ~STATUS1_RSTDONE) != 0) { + // not safe to proceed, must initiate reset + ESP_LOGD(TAG, "IRQ1 asserted for !RSTDONE, resetting device"); + this->reset_device_(); + return; + } + if ((status1 & STATUS1_RSTDONE) == STATUS1_RSTDONE) { + // safe to proceed, device has just completed reset cycle + ESP_LOGD(TAG, "Acknowledging RSTDONE"); + this->write_u32_register16_(STATUS0, 0xFFFF); + this->write_u32_register16_(STATUS1, 0xFFFF); + this->init_device_(); + return; + } + } + + this->reset_device_(); +} + +void ADE7880::loop() { + // check for completion of a reset cycle + if (!this->store_.reset_done) { + return; + } + + ESP_LOGD(TAG, "Acknowledging RSTDONE"); + this->write_u32_register16_(STATUS0, 0xFFFF); + this->write_u32_register16_(STATUS1, 0xFFFF); + this->init_device_(); + this->store_.reset_done = false; + this->store_.reset_pending = false; +} + +template +void ADE7880::update_sensor_from_s24zp_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f) { + if (sensor == nullptr) { + return; + } + + float val = this->read_s24zp_register16_(a_register); + sensor->publish_state(f(val)); +} + +template +void ADE7880::update_sensor_from_s16_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f) { + if (sensor == nullptr) { + return; + } + + float val = this->read_s16_register16_(a_register); + sensor->publish_state(f(val)); +} + +template +void ADE7880::update_sensor_from_s32_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f) { + if (sensor == nullptr) { + return; + } + + float val = this->read_s32_register16_(a_register); + sensor->publish_state(f(val)); +} + +void ADE7880::update() { + if (this->store_.reset_pending) { + return; + } + + auto start = millis(); + + if (this->channel_n_ != nullptr) { + auto *chan = this->channel_n_; + this->update_sensor_from_s24zp_register16_(chan->current, NIRMS, [](float val) { return val / 100000.0f; }); + } + + if (this->channel_a_ != nullptr) { + auto *chan = this->channel_a_; + this->update_sensor_from_s24zp_register16_(chan->current, AIRMS, [](float val) { return val / 100000.0f; }); + this->update_sensor_from_s24zp_register16_(chan->voltage, BVRMS, [](float val) { return val / 10000.0f; }); + this->update_sensor_from_s24zp_register16_(chan->active_power, AWATT, [](float val) { return val / 100.0f; }); + this->update_sensor_from_s24zp_register16_(chan->apparent_power, AVA, [](float val) { return val / 100.0f; }); + this->update_sensor_from_s16_register16_(chan->power_factor, APF, + [](float val) { return std::abs(val / -327.68f); }); + this->update_sensor_from_s32_register16_(chan->forward_active_energy, AFWATTHR, [&chan](float val) { + return chan->forward_active_energy_total += val / 14400.0f; + }); + this->update_sensor_from_s32_register16_(chan->reverse_active_energy, AFWATTHR, [&chan](float val) { + return chan->reverse_active_energy_total += val / 14400.0f; + }); + } + + if (this->channel_b_ != nullptr) { + auto *chan = this->channel_b_; + this->update_sensor_from_s24zp_register16_(chan->current, BIRMS, [](float val) { return val / 100000.0f; }); + this->update_sensor_from_s24zp_register16_(chan->voltage, BVRMS, [](float val) { return val / 10000.0f; }); + this->update_sensor_from_s24zp_register16_(chan->active_power, BWATT, [](float val) { return val / 100.0f; }); + this->update_sensor_from_s24zp_register16_(chan->apparent_power, BVA, [](float val) { return val / 100.0f; }); + this->update_sensor_from_s16_register16_(chan->power_factor, BPF, + [](float val) { return std::abs(val / -327.68f); }); + this->update_sensor_from_s32_register16_(chan->forward_active_energy, BFWATTHR, [&chan](float val) { + return chan->forward_active_energy_total += val / 14400.0f; + }); + this->update_sensor_from_s32_register16_(chan->reverse_active_energy, BFWATTHR, [&chan](float val) { + return chan->reverse_active_energy_total += val / 14400.0f; + }); + } + + if (this->channel_c_ != nullptr) { + auto *chan = this->channel_c_; + this->update_sensor_from_s24zp_register16_(chan->current, CIRMS, [](float val) { return val / 100000.0f; }); + this->update_sensor_from_s24zp_register16_(chan->voltage, CVRMS, [](float val) { return val / 10000.0f; }); + this->update_sensor_from_s24zp_register16_(chan->active_power, CWATT, [](float val) { return val / 100.0f; }); + this->update_sensor_from_s24zp_register16_(chan->apparent_power, CVA, [](float val) { return val / 100.0f; }); + this->update_sensor_from_s16_register16_(chan->power_factor, CPF, + [](float val) { return std::abs(val / -327.68f); }); + this->update_sensor_from_s32_register16_(chan->forward_active_energy, CFWATTHR, [&chan](float val) { + return chan->forward_active_energy_total += val / 14400.0f; + }); + this->update_sensor_from_s32_register16_(chan->reverse_active_energy, CFWATTHR, [&chan](float val) { + return chan->reverse_active_energy_total += val / 14400.0f; + }); + } + + ESP_LOGD(TAG, "update took %u ms", millis() - start); +} + +void ADE7880::dump_config() { + ESP_LOGCONFIG(TAG, "ADE7880:"); + LOG_PIN(" IRQ0 Pin: ", this->irq0_pin_); + LOG_PIN(" IRQ1 Pin: ", this->irq1_pin_); + LOG_PIN(" RESET Pin: ", this->reset_pin_); + ESP_LOGCONFIG(TAG, " Frequency: %.0f Hz", this->frequency_); + + if (this->channel_a_ != nullptr) { + ESP_LOGCONFIG(TAG, " Phase A:"); + LOG_SENSOR(" ", "Current", this->channel_a_->current); + LOG_SENSOR(" ", "Voltage", this->channel_a_->voltage); + LOG_SENSOR(" ", "Active Power", this->channel_a_->active_power); + LOG_SENSOR(" ", "Apparent Power", this->channel_a_->apparent_power); + LOG_SENSOR(" ", "Power Factor", this->channel_a_->power_factor); + LOG_SENSOR(" ", "Forward Active Energy", this->channel_a_->forward_active_energy); + LOG_SENSOR(" ", "Reverse Active Energy", this->channel_a_->reverse_active_energy); + ESP_LOGCONFIG(TAG, " Calibration:"); + ESP_LOGCONFIG(TAG, " Current: %u", this->channel_a_->current_gain_calibration); + ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_a_->voltage_gain_calibration); + ESP_LOGCONFIG(TAG, " Power: %d", this->channel_a_->power_gain_calibration); + ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_a_->phase_angle_calibration); + } + + if (this->channel_b_ != nullptr) { + ESP_LOGCONFIG(TAG, " Phase B:"); + LOG_SENSOR(" ", "Current", this->channel_b_->current); + LOG_SENSOR(" ", "Voltage", this->channel_b_->voltage); + LOG_SENSOR(" ", "Active Power", this->channel_b_->active_power); + LOG_SENSOR(" ", "Apparent Power", this->channel_b_->apparent_power); + LOG_SENSOR(" ", "Power Factor", this->channel_b_->power_factor); + LOG_SENSOR(" ", "Forward Active Energy", this->channel_b_->forward_active_energy); + LOG_SENSOR(" ", "Reverse Active Energy", this->channel_b_->reverse_active_energy); + ESP_LOGCONFIG(TAG, " Calibration:"); + ESP_LOGCONFIG(TAG, " Current: %u", this->channel_b_->current_gain_calibration); + ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_b_->voltage_gain_calibration); + ESP_LOGCONFIG(TAG, " Power: %d", this->channel_b_->power_gain_calibration); + ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_b_->phase_angle_calibration); + } + + if (this->channel_c_ != nullptr) { + ESP_LOGCONFIG(TAG, " Phase C:"); + LOG_SENSOR(" ", "Current", this->channel_c_->current); + LOG_SENSOR(" ", "Voltage", this->channel_c_->voltage); + LOG_SENSOR(" ", "Active Power", this->channel_c_->active_power); + LOG_SENSOR(" ", "Apparent Power", this->channel_c_->apparent_power); + LOG_SENSOR(" ", "Power Factor", this->channel_c_->power_factor); + LOG_SENSOR(" ", "Forward Active Energy", this->channel_c_->forward_active_energy); + LOG_SENSOR(" ", "Reverse Active Energy", this->channel_c_->reverse_active_energy); + ESP_LOGCONFIG(TAG, " Calibration:"); + ESP_LOGCONFIG(TAG, " Current: %u", this->channel_c_->current_gain_calibration); + ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_c_->voltage_gain_calibration); + ESP_LOGCONFIG(TAG, " Power: %d", this->channel_c_->power_gain_calibration); + ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_c_->phase_angle_calibration); + } + + if (this->channel_n_ != nullptr) { + ESP_LOGCONFIG(TAG, " Neutral:"); + LOG_SENSOR(" ", "Current", this->channel_n_->current); + ESP_LOGCONFIG(TAG, " Calibration:"); + ESP_LOGCONFIG(TAG, " Current: %u", this->channel_n_->current_gain_calibration); + } + + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); +} + +void ADE7880::calibrate_s10zp_reading_(uint16_t a_register, int16_t calibration) { + if (calibration == 0) { + return; + } + + this->write_s10zp_register16_(a_register, calibration); +} + +void ADE7880::calibrate_s24zpse_reading_(uint16_t a_register, int32_t calibration) { + if (calibration == 0) { + return; + } + + this->write_s24zpse_register16_(a_register, calibration); +} + +void ADE7880::init_device_() { + this->write_u8_register16_(CONFIG2, CONFIG2_I2C_LOCK); + + this->write_u16_register16_(GAIN, 0); + + if (this->frequency_ > 55) { + this->write_u16_register16_(COMPMODE, COMPMODE_DEFAULT | COMPMODE_SELFREQ); + } + + if (this->channel_n_ != nullptr) { + this->calibrate_s24zpse_reading_(NIGAIN, this->channel_n_->current_gain_calibration); + } + + if (this->channel_a_ != nullptr) { + this->calibrate_s24zpse_reading_(AIGAIN, this->channel_a_->current_gain_calibration); + this->calibrate_s24zpse_reading_(AVGAIN, this->channel_a_->voltage_gain_calibration); + this->calibrate_s24zpse_reading_(APGAIN, this->channel_a_->power_gain_calibration); + this->calibrate_s10zp_reading_(APHCAL, this->channel_a_->phase_angle_calibration); + } + + if (this->channel_b_ != nullptr) { + this->calibrate_s24zpse_reading_(BIGAIN, this->channel_b_->current_gain_calibration); + this->calibrate_s24zpse_reading_(BVGAIN, this->channel_b_->voltage_gain_calibration); + this->calibrate_s24zpse_reading_(BPGAIN, this->channel_b_->power_gain_calibration); + this->calibrate_s10zp_reading_(BPHCAL, this->channel_b_->phase_angle_calibration); + } + + if (this->channel_c_ != nullptr) { + this->calibrate_s24zpse_reading_(CIGAIN, this->channel_c_->current_gain_calibration); + this->calibrate_s24zpse_reading_(CVGAIN, this->channel_c_->voltage_gain_calibration); + this->calibrate_s24zpse_reading_(CPGAIN, this->channel_c_->power_gain_calibration); + this->calibrate_s10zp_reading_(CPHCAL, this->channel_c_->phase_angle_calibration); + } + + // write three default values to data memory RAM to flush the I2C write queue + this->write_s32_register16_(VLEVEL, 0); + this->write_s32_register16_(VLEVEL, 0); + this->write_s32_register16_(VLEVEL, 0); + + this->write_u8_register16_(DSPWP_SEL, DSPWP_SEL_SET); + this->write_u8_register16_(DSPWP_SET, DSPWP_SET_RO); + this->write_u16_register16_(RUN, RUN_ENABLE); +} + +void ADE7880::reset_device_() { + if (this->reset_pin_ != nullptr) { + ESP_LOGD(TAG, "Reset device using RESET pin"); + this->reset_pin_->digital_write(false); + delay(1); + this->reset_pin_->digital_write(true); + } else { + ESP_LOGD(TAG, "Reset device using SWRST command"); + this->write_u16_register16_(CONFIG, CONFIG_SWRST); + } + this->store_.reset_pending = true; +} + +} // namespace ade7880 +} // namespace esphome diff --git a/esphome/components/ade7880/ade7880.h b/esphome/components/ade7880/ade7880.h new file mode 100644 index 0000000000..a565357dc5 --- /dev/null +++ b/esphome/components/ade7880/ade7880.h @@ -0,0 +1,131 @@ +#pragma once + +// This component was developed using knowledge gathered by a number +// of people who reverse-engineered the Shelly 3EM: +// +// @AndreKR on GitHub +// Axel (@Axel830 on GitHub) +// Marko (@goodkiller on GitHub) +// Michaël Piron (@michaelpiron on GitHub) +// Theo Arends (@arendst on GitHub) + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" + +#include "ade7880_registers.h" + +namespace esphome { +namespace ade7880 { + +struct NeutralChannel { + void set_current(sensor::Sensor *sens) { this->current = sens; } + + void set_current_gain_calibration(int32_t val) { this->current_gain_calibration = val; } + + sensor::Sensor *current{nullptr}; + int32_t current_gain_calibration{0}; +}; + +struct PowerChannel { + void set_current(sensor::Sensor *sens) { this->current = sens; } + void set_voltage(sensor::Sensor *sens) { this->voltage = sens; } + void set_active_power(sensor::Sensor *sens) { this->active_power = sens; } + void set_apparent_power(sensor::Sensor *sens) { this->apparent_power = sens; } + void set_power_factor(sensor::Sensor *sens) { this->power_factor = sens; } + void set_forward_active_energy(sensor::Sensor *sens) { this->forward_active_energy = sens; } + void set_reverse_active_energy(sensor::Sensor *sens) { this->reverse_active_energy = sens; } + + void set_current_gain_calibration(int32_t val) { this->current_gain_calibration = val; } + void set_voltage_gain_calibration(int32_t val) { this->voltage_gain_calibration = val; } + void set_power_gain_calibration(int32_t val) { this->power_gain_calibration = val; } + void set_phase_angle_calibration(int32_t val) { this->phase_angle_calibration = val; } + + sensor::Sensor *current{nullptr}; + sensor::Sensor *voltage{nullptr}; + sensor::Sensor *active_power{nullptr}; + sensor::Sensor *apparent_power{nullptr}; + sensor::Sensor *power_factor{nullptr}; + sensor::Sensor *forward_active_energy{nullptr}; + sensor::Sensor *reverse_active_energy{nullptr}; + int32_t current_gain_calibration{0}; + int32_t voltage_gain_calibration{0}; + int32_t power_gain_calibration{0}; + uint16_t phase_angle_calibration{0}; + float forward_active_energy_total{0}; + float reverse_active_energy_total{0}; +}; + +// Store data in a class that doesn't use multiple-inheritance (no vtables in flash!) +struct ADE7880Store { + volatile bool reset_done{false}; + bool reset_pending{false}; + ISRInternalGPIOPin irq1_pin; + + static void gpio_intr(ADE7880Store *arg); +}; + +class ADE7880 : public i2c::I2CDevice, public PollingComponent { + public: + void set_irq0_pin(InternalGPIOPin *pin) { this->irq0_pin_ = pin; } + void set_irq1_pin(InternalGPIOPin *pin) { this->irq1_pin_ = pin; } + void set_reset_pin(InternalGPIOPin *pin) { this->reset_pin_ = pin; } + void set_frequency(float frequency) { this->frequency_ = frequency; } + void set_channel_n(NeutralChannel *channel) { this->channel_n_ = channel; } + void set_channel_a(PowerChannel *channel) { this->channel_a_ = channel; } + void set_channel_b(PowerChannel *channel) { this->channel_b_ = channel; } + void set_channel_c(PowerChannel *channel) { this->channel_c_ = channel; } + + void setup() override; + + void loop() override; + + void update() override; + + void dump_config() override; + + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + ADE7880Store store_{}; + InternalGPIOPin *irq0_pin_{nullptr}; + InternalGPIOPin *irq1_pin_{nullptr}; + InternalGPIOPin *reset_pin_{nullptr}; + float frequency_; + NeutralChannel *channel_n_{nullptr}; + PowerChannel *channel_a_{nullptr}; + PowerChannel *channel_b_{nullptr}; + PowerChannel *channel_c_{nullptr}; + + void calibrate_s10zp_reading_(uint16_t a_register, int16_t calibration); + void calibrate_s24zpse_reading_(uint16_t a_register, int32_t calibration); + + void init_device_(); + + // each of these functions allow the caller to pass in a lambda (or any other callable) + // which modifies the value read from the register before it is passed to the sensor + // the callable will be passed a 'float' value and is expected to return a 'float' + template void update_sensor_from_s24zp_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f); + template void update_sensor_from_s16_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f); + template void update_sensor_from_s32_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f); + + void reset_device_(); + + uint8_t read_u8_register16_(uint16_t a_register); + int16_t read_s16_register16_(uint16_t a_register); + uint16_t read_u16_register16_(uint16_t a_register); + int32_t read_s24zp_register16_(uint16_t a_register); + int32_t read_s32_register16_(uint16_t a_register); + uint32_t read_u32_register16_(uint16_t a_register); + + void write_u8_register16_(uint16_t a_register, uint8_t value); + void write_s10zp_register16_(uint16_t a_register, int16_t value); + void write_u16_register16_(uint16_t a_register, uint16_t value); + void write_s24zpse_register16_(uint16_t a_register, int32_t value); + void write_s32_register16_(uint16_t a_register, int32_t value); + void write_u32_register16_(uint16_t a_register, uint32_t value); +}; + +} // namespace ade7880 +} // namespace esphome diff --git a/esphome/components/ade7880/ade7880_i2c.cpp b/esphome/components/ade7880/ade7880_i2c.cpp new file mode 100644 index 0000000000..fae20f175d --- /dev/null +++ b/esphome/components/ade7880/ade7880_i2c.cpp @@ -0,0 +1,101 @@ +// This component was developed using knowledge gathered by a number +// of people who reverse-engineered the Shelly 3EM: +// +// @AndreKR on GitHub +// Axel (@Axel830 on GitHub) +// Marko (@goodkiller on GitHub) +// Michaël Piron (@michaelpiron on GitHub) +// Theo Arends (@arendst on GitHub) + +#include "ade7880.h" + +namespace esphome { +namespace ade7880 { + +// adapted from https://stackoverflow.com/a/55912127/1886371 +template inline T sign_extend(const T &v) noexcept { + using S = struct { signed Val : Bits; }; + return reinterpret_cast(&v)->Val; +} + +// Register types +// unsigned 8-bit (uint8_t) +// signed 10-bit - 16-bit ZP on wire (int16_t, needs sign extension) +// unsigned 16-bit (uint16_t) +// unsigned 20-bit - 32-bit ZP on wire (uint32_t) +// signed 24-bit - 32-bit ZPSE on wire (int32_t, needs sign extension) +// signed 24-bit - 32-bit ZP on wire (int32_t, needs sign extension) +// signed 24-bit - 32-bit SE on wire (int32_t) +// signed 28-bit - 32-bit ZP on wire (int32_t, needs sign extension) +// unsigned 32-bit (uint32_t) +// signed 32-bit (int32_t) + +uint8_t ADE7880::read_u8_register16_(uint16_t a_register) { + uint8_t in; + this->read_register16(a_register, &in, sizeof(in)); + return in; +} + +int16_t ADE7880::read_s16_register16_(uint16_t a_register) { + int16_t in; + this->read_register16(a_register, reinterpret_cast(&in), sizeof(in)); + return convert_big_endian(in); +} + +uint16_t ADE7880::read_u16_register16_(uint16_t a_register) { + uint16_t in; + this->read_register16(a_register, reinterpret_cast(&in), sizeof(in)); + return convert_big_endian(in); +} + +int32_t ADE7880::read_s24zp_register16_(uint16_t a_register) { + // s24zp means 24 bit signed value in the lower 24 bits of a 32-bit register + int32_t in; + this->read_register16(a_register, reinterpret_cast(&in), sizeof(in)); + return sign_extend<24>(convert_big_endian(in)); +} + +int32_t ADE7880::read_s32_register16_(uint16_t a_register) { + int32_t in; + this->read_register16(a_register, reinterpret_cast(&in), sizeof(in)); + return convert_big_endian(in); +} + +uint32_t ADE7880::read_u32_register16_(uint16_t a_register) { + uint32_t in; + this->read_register16(a_register, reinterpret_cast(&in), sizeof(in)); + return convert_big_endian(in); +} + +void ADE7880::write_u8_register16_(uint16_t a_register, uint8_t value) { + this->write_register16(a_register, &value, sizeof(value)); +} + +void ADE7880::write_s10zp_register16_(uint16_t a_register, int16_t value) { + int16_t out = convert_big_endian(value & 0x03FF); + this->write_register16(a_register, reinterpret_cast(&out), sizeof(out)); +} + +void ADE7880::write_u16_register16_(uint16_t a_register, uint16_t value) { + uint16_t out = convert_big_endian(value); + this->write_register16(a_register, reinterpret_cast(&out), sizeof(out)); +} + +void ADE7880::write_s24zpse_register16_(uint16_t a_register, int32_t value) { + // s24zpse means a 24-bit signed value, sign-extended to 28 bits, in the lower 28 bits of a 32-bit register + int32_t out = convert_big_endian(value & 0x0FFFFFFF); + this->write_register16(a_register, reinterpret_cast(&out), sizeof(out)); +} + +void ADE7880::write_s32_register16_(uint16_t a_register, int32_t value) { + int32_t out = convert_big_endian(value); + this->write_register16(a_register, reinterpret_cast(&out), sizeof(out)); +} + +void ADE7880::write_u32_register16_(uint16_t a_register, uint32_t value) { + uint32_t out = convert_big_endian(value); + this->write_register16(a_register, reinterpret_cast(&out), sizeof(out)); +} + +} // namespace ade7880 +} // namespace esphome diff --git a/esphome/components/ade7880/ade7880_registers.h b/esphome/components/ade7880/ade7880_registers.h new file mode 100644 index 0000000000..8b5b68abb0 --- /dev/null +++ b/esphome/components/ade7880/ade7880_registers.h @@ -0,0 +1,243 @@ +#pragma once + +// This file is a modified version of the one created by Michaël Piron (@michaelpiron on GitHub) + +// Source: https://www.analog.com/media/en/technical-documentation/application-notes/AN-1127.pdf + +namespace esphome { +namespace ade7880 { + +// DSP Data Memory RAM registers +constexpr uint16_t AIGAIN = 0x4380; +constexpr uint16_t AVGAIN = 0x4381; +constexpr uint16_t BIGAIN = 0x4382; +constexpr uint16_t BVGAIN = 0x4383; +constexpr uint16_t CIGAIN = 0x4384; +constexpr uint16_t CVGAIN = 0x4385; +constexpr uint16_t NIGAIN = 0x4386; + +constexpr uint16_t DICOEFF = 0x4388; + +constexpr uint16_t APGAIN = 0x4389; +constexpr uint16_t AWATTOS = 0x438A; +constexpr uint16_t BPGAIN = 0x438B; +constexpr uint16_t BWATTOS = 0x438C; +constexpr uint16_t CPGAIN = 0x438D; +constexpr uint16_t CWATTOS = 0x438E; +constexpr uint16_t AIRMSOS = 0x438F; +constexpr uint16_t AVRMSOS = 0x4390; +constexpr uint16_t BIRMSOS = 0x4391; +constexpr uint16_t BVRMSOS = 0x4392; +constexpr uint16_t CIRMSOS = 0x4393; +constexpr uint16_t CVRMSOS = 0x4394; +constexpr uint16_t NIRMSOS = 0x4395; +constexpr uint16_t HPGAIN = 0x4398; +constexpr uint16_t ISUMLVL = 0x4399; + +constexpr uint16_t VLEVEL = 0x439F; + +constexpr uint16_t AFWATTOS = 0x43A2; +constexpr uint16_t BFWATTOS = 0x43A3; +constexpr uint16_t CFWATTOS = 0x43A4; + +constexpr uint16_t AFVAROS = 0x43A5; +constexpr uint16_t BFVAROS = 0x43A6; +constexpr uint16_t CFVAROS = 0x43A7; + +constexpr uint16_t AFIRMSOS = 0x43A8; +constexpr uint16_t BFIRMSOS = 0x43A9; +constexpr uint16_t CFIRMSOS = 0x43AA; + +constexpr uint16_t AFVRMSOS = 0x43AB; +constexpr uint16_t BFVRMSOS = 0x43AC; +constexpr uint16_t CFVRMSOS = 0x43AD; + +constexpr uint16_t HXWATTOS = 0x43AE; +constexpr uint16_t HYWATTOS = 0x43AF; +constexpr uint16_t HZWATTOS = 0x43B0; +constexpr uint16_t HXVAROS = 0x43B1; +constexpr uint16_t HYVAROS = 0x43B2; +constexpr uint16_t HZVAROS = 0x43B3; + +constexpr uint16_t HXIRMSOS = 0x43B4; +constexpr uint16_t HYIRMSOS = 0x43B5; +constexpr uint16_t HZIRMSOS = 0x43B6; +constexpr uint16_t HXVRMSOS = 0x43B7; +constexpr uint16_t HYVRMSOS = 0x43B8; +constexpr uint16_t HZVRMSOS = 0x43B9; + +constexpr uint16_t AIRMS = 0x43C0; +constexpr uint16_t AVRMS = 0x43C1; +constexpr uint16_t BIRMS = 0x43C2; +constexpr uint16_t BVRMS = 0x43C3; +constexpr uint16_t CIRMS = 0x43C4; +constexpr uint16_t CVRMS = 0x43C5; +constexpr uint16_t NIRMS = 0x43C6; + +constexpr uint16_t ISUM = 0x43C7; + +// Internal DSP Memory RAM registers +constexpr uint16_t RUN = 0xE228; + +constexpr uint16_t AWATTHR = 0xE400; +constexpr uint16_t BWATTHR = 0xE401; +constexpr uint16_t CWATTHR = 0xE402; +constexpr uint16_t AFWATTHR = 0xE403; +constexpr uint16_t BFWATTHR = 0xE404; +constexpr uint16_t CFWATTHR = 0xE405; +constexpr uint16_t AFVARHR = 0xE409; +constexpr uint16_t BFVARHR = 0xE40A; +constexpr uint16_t CFVARHR = 0xE40B; + +constexpr uint16_t AVAHR = 0xE40C; +constexpr uint16_t BVAHR = 0xE40D; +constexpr uint16_t CVAHR = 0xE40E; + +constexpr uint16_t IPEAK = 0xE500; +constexpr uint16_t VPEAK = 0xE501; + +constexpr uint16_t STATUS0 = 0xE502; +constexpr uint16_t STATUS1 = 0xE503; + +constexpr uint16_t AIMAV = 0xE504; +constexpr uint16_t BIMAV = 0xE505; +constexpr uint16_t CIMAV = 0xE506; + +constexpr uint16_t OILVL = 0xE507; +constexpr uint16_t OVLVL = 0xE508; +constexpr uint16_t SAGLVL = 0xE509; +constexpr uint16_t MASK0 = 0xE50A; +constexpr uint16_t MASK1 = 0xE50B; + +constexpr uint16_t IAWV = 0xE50C; +constexpr uint16_t IBWV = 0xE50D; +constexpr uint16_t ICWV = 0xE50E; +constexpr uint16_t INWV = 0xE50F; +constexpr uint16_t VAWV = 0xE510; +constexpr uint16_t VBWV = 0xE511; +constexpr uint16_t VCWV = 0xE512; + +constexpr uint16_t AWATT = 0xE513; +constexpr uint16_t BWATT = 0xE514; +constexpr uint16_t CWATT = 0xE515; + +constexpr uint16_t AFVAR = 0xE516; +constexpr uint16_t BFVAR = 0xE517; +constexpr uint16_t CFVAR = 0xE518; + +constexpr uint16_t AVA = 0xE519; +constexpr uint16_t BVA = 0xE51A; +constexpr uint16_t CVA = 0xE51B; + +constexpr uint16_t CHECKSUM = 0xE51F; +constexpr uint16_t VNOM = 0xE520; +constexpr uint16_t LAST_RWDATA_24BIT = 0xE5FF; +constexpr uint16_t PHSTATUS = 0xE600; +constexpr uint16_t ANGLE0 = 0xE601; +constexpr uint16_t ANGLE1 = 0xE602; +constexpr uint16_t ANGLE2 = 0xE603; +constexpr uint16_t PHNOLOAD = 0xE608; +constexpr uint16_t LINECYC = 0xE60C; +constexpr uint16_t ZXTOUT = 0xE60D; +constexpr uint16_t COMPMODE = 0xE60E; +constexpr uint16_t GAIN = 0xE60F; +constexpr uint16_t CFMODE = 0xE610; +constexpr uint16_t CF1DEN = 0xE611; +constexpr uint16_t CF2DEN = 0xE612; +constexpr uint16_t CF3DEN = 0xE613; +constexpr uint16_t APHCAL = 0xE614; +constexpr uint16_t BPHCAL = 0xE615; +constexpr uint16_t CPHCAL = 0xE616; +constexpr uint16_t PHSIGN = 0xE617; +constexpr uint16_t CONFIG = 0xE618; +constexpr uint16_t MMODE = 0xE700; +constexpr uint16_t ACCMODE = 0xE701; +constexpr uint16_t LCYCMODE = 0xE702; +constexpr uint16_t PEAKCYC = 0xE703; +constexpr uint16_t SAGCYC = 0xE704; +constexpr uint16_t CFCYC = 0xE705; +constexpr uint16_t HSDC_CFG = 0xE706; +constexpr uint16_t VERSION = 0xE707; +constexpr uint16_t DSPWP_SET = 0xE7E3; +constexpr uint16_t LAST_RWDATA_8BIT = 0xE7FD; +constexpr uint16_t DSPWP_SEL = 0xE7FE; +constexpr uint16_t FVRMS = 0xE880; +constexpr uint16_t FIRMS = 0xE881; +constexpr uint16_t FWATT = 0xE882; +constexpr uint16_t FVAR = 0xE883; +constexpr uint16_t FVA = 0xE884; +constexpr uint16_t FPF = 0xE885; +constexpr uint16_t VTHDN = 0xE886; +constexpr uint16_t ITHDN = 0xE887; +constexpr uint16_t HXVRMS = 0xE888; +constexpr uint16_t HXIRMS = 0xE889; +constexpr uint16_t HXWATT = 0xE88A; +constexpr uint16_t HXVAR = 0xE88B; +constexpr uint16_t HXVA = 0xE88C; +constexpr uint16_t HXPF = 0xE88D; +constexpr uint16_t HXVHD = 0xE88E; +constexpr uint16_t HXIHD = 0xE88F; +constexpr uint16_t HYVRMS = 0xE890; +constexpr uint16_t HYIRMS = 0xE891; +constexpr uint16_t HYWATT = 0xE892; +constexpr uint16_t HYVAR = 0xE893; +constexpr uint16_t HYVA = 0xE894; +constexpr uint16_t HYPF = 0xE895; +constexpr uint16_t HYVHD = 0xE896; +constexpr uint16_t HYIHD = 0xE897; +constexpr uint16_t HZVRMS = 0xE898; +constexpr uint16_t HZIRMS = 0xE899; +constexpr uint16_t HZWATT = 0xE89A; +constexpr uint16_t HZVAR = 0xE89B; +constexpr uint16_t HZVA = 0xE89C; +constexpr uint16_t HZPF = 0xE89D; +constexpr uint16_t HZVHD = 0xE89E; +constexpr uint16_t HZIHD = 0xE89F; +constexpr uint16_t HCONFIG = 0xE900; +constexpr uint16_t APF = 0xE902; +constexpr uint16_t BPF = 0xE903; +constexpr uint16_t CPF = 0xE904; +constexpr uint16_t APERIOD = 0xE905; +constexpr uint16_t BPERIOD = 0xE906; +constexpr uint16_t CPERIOD = 0xE907; +constexpr uint16_t APNOLOAD = 0xE908; +constexpr uint16_t VARNOLOAD = 0xE909; +constexpr uint16_t VANOLOAD = 0xE90A; +constexpr uint16_t LAST_ADD = 0xE9FE; +constexpr uint16_t LAST_RWDATA_16BIT = 0xE9FF; +constexpr uint16_t CONFIG3 = 0xEA00; +constexpr uint16_t LAST_OP = 0xEA01; +constexpr uint16_t WTHR = 0xEA02; +constexpr uint16_t VARTHR = 0xEA03; +constexpr uint16_t VATHR = 0xEA04; + +constexpr uint16_t HX_REG = 0xEA08; +constexpr uint16_t HY_REG = 0xEA09; +constexpr uint16_t HZ_REG = 0xEA0A; +constexpr uint16_t LPOILVL = 0xEC00; +constexpr uint16_t CONFIG2 = 0xEC01; + +// STATUS1 Register Bits +constexpr uint32_t STATUS1_RSTDONE = (1 << 15); + +// CONFIG Register Bits +constexpr uint16_t CONFIG_SWRST = (1 << 7); + +// CONFIG2 Register Bits +constexpr uint8_t CONFIG2_I2C_LOCK = (1 << 1); + +// COMPMODE Register Bits +constexpr uint16_t COMPMODE_DEFAULT = 0x01FF; +constexpr uint16_t COMPMODE_SELFREQ = (1 << 14); + +// RUN Register Bits +constexpr uint16_t RUN_ENABLE = (1 << 0); + +// DSPWP_SET Register Bits +constexpr uint8_t DSPWP_SET_RO = (1 << 7); + +// DSPWP_SEL Register Bits +constexpr uint8_t DSPWP_SEL_SET = 0xAD; + +} // namespace ade7880 +} // namespace esphome diff --git a/esphome/components/ade7880/sensor.py b/esphome/components/ade7880/sensor.py new file mode 100644 index 0000000000..42a2b0d3fc --- /dev/null +++ b/esphome/components/ade7880/sensor.py @@ -0,0 +1,290 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, i2c +from esphome import pins +from esphome.const import ( + CONF_ACTIVE_POWER, + CONF_APPARENT_POWER, + CONF_CALIBRATION, + CONF_CURRENT, + CONF_FORWARD_ACTIVE_ENERGY, + CONF_FREQUENCY, + CONF_ID, + CONF_NAME, + CONF_PHASE_A, + CONF_PHASE_ANGLE, + CONF_PHASE_B, + CONF_PHASE_C, + CONF_POWER_FACTOR, + CONF_RESET_PIN, + CONF_REVERSE_ACTIVE_ENERGY, + CONF_VOLTAGE, + DEVICE_CLASS_APPARENT_POWER, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_AMPERE, + UNIT_PERCENT, + UNIT_VOLT, + UNIT_VOLT_AMPS, + UNIT_VOLT_AMPS_REACTIVE_HOURS, + UNIT_WATT, + UNIT_WATT_HOURS, +) + +DEPENDENCIES = ["i2c"] + +ade7880_ns = cg.esphome_ns.namespace("ade7880") +ADE7880 = ade7880_ns.class_("ADE7880", cg.PollingComponent, i2c.I2CDevice) +NeutralChannel = ade7880_ns.struct("NeutralChannel") +PowerChannel = ade7880_ns.struct("PowerChannel") + +CONF_CURRENT_GAIN = "current_gain" +CONF_IRQ0_PIN = "irq0_pin" +CONF_IRQ1_PIN = "irq1_pin" +CONF_POWER_GAIN = "power_gain" +CONF_VOLTAGE_GAIN = "voltage_gain" + +CONF_NEUTRAL = "neutral" + +NEUTRAL_CHANNEL_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(NeutralChannel), + cv.Optional(CONF_NAME): cv.string_strict, + cv.Required(CONF_CURRENT): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Required(CONF_CALIBRATION): cv.Schema( + { + cv.Required(CONF_CURRENT_GAIN): cv.int_, + }, + ), + } +) + +POWER_CHANNEL_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(PowerChannel), + cv.Optional(CONF_NAME): cv.string_strict, + cv.Optional(CONF_VOLTAGE): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_CURRENT): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_ACTIVE_POWER): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_APPARENT_POWER): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT_AMPS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_POWER_FACTOR): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + key=CONF_NAME, + ), + cv.Required(CONF_CALIBRATION): cv.Schema( + { + cv.Required(CONF_CURRENT_GAIN): cv.int_, + cv.Required(CONF_VOLTAGE_GAIN): cv.int_, + cv.Required(CONF_POWER_GAIN): cv.int_, + cv.Required(CONF_PHASE_ANGLE): cv.int_, + }, + ), + } +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ADE7880), + cv.Optional(CONF_FREQUENCY, default="50Hz"): cv.All( + cv.frequency, cv.Range(min=45.0, max=66.0) + ), + cv.Optional(CONF_IRQ0_PIN): pins.internal_gpio_input_pin_schema, + cv.Required(CONF_IRQ1_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_PHASE_A): POWER_CHANNEL_SCHEMA, + cv.Optional(CONF_PHASE_B): POWER_CHANNEL_SCHEMA, + cv.Optional(CONF_PHASE_C): POWER_CHANNEL_SCHEMA, + cv.Optional(CONF_NEUTRAL): NEUTRAL_CHANNEL_SCHEMA, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x38)) +) + + +async def neutral_channel(config): + var = cg.new_Pvariable(config[CONF_ID]) + + current = config[CONF_CURRENT] + sens = await sensor.new_sensor(current) + cg.add(var.set_current(sens)) + + cg.add( + var.set_current_gain_calibration(config[CONF_CALIBRATION][CONF_CURRENT_GAIN]) + ) + + return var + + +async def power_channel(config): + var = cg.new_Pvariable(config[CONF_ID]) + + for sensor_type in [ + CONF_CURRENT, + CONF_VOLTAGE, + CONF_ACTIVE_POWER, + CONF_APPARENT_POWER, + CONF_POWER_FACTOR, + CONF_FORWARD_ACTIVE_ENERGY, + CONF_REVERSE_ACTIVE_ENERGY, + ]: + if conf := config.get(sensor_type): + sens = await sensor.new_sensor(conf) + cg.add(getattr(var, f"set_{sensor_type}")(sens)) + + for calib_type in [ + CONF_CURRENT_GAIN, + CONF_VOLTAGE_GAIN, + CONF_POWER_GAIN, + CONF_PHASE_ANGLE, + ]: + cg.add( + getattr(var, f"set_{calib_type}_calibration")( + config[CONF_CALIBRATION][calib_type] + ) + ) + + return var + + +def final_validate(config): + for channel in [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]: + if channel := config.get(channel): + channel_name = channel.get(CONF_NAME) + + for sensor_type in [ + CONF_CURRENT, + CONF_VOLTAGE, + CONF_ACTIVE_POWER, + CONF_APPARENT_POWER, + CONF_POWER_FACTOR, + CONF_FORWARD_ACTIVE_ENERGY, + CONF_REVERSE_ACTIVE_ENERGY, + ]: + if conf := channel.get(sensor_type): + sensor_name = conf.get(CONF_NAME) + if ( + sensor_name + and channel_name + and not sensor_name.startswith(channel_name) + ): + conf[CONF_NAME] = f"{channel_name} {sensor_name}" + + if channel := config.get(CONF_NEUTRAL): + channel_name = channel.get(CONF_NAME) + if conf := channel.get(CONF_CURRENT): + sensor_name = conf.get(CONF_NAME) + if ( + sensor_name + and channel_name + and not sensor_name.startswith(channel_name) + ): + conf[CONF_NAME] = f"{channel_name} {sensor_name}" + + +FINAL_VALIDATE_SCHEMA = final_validate + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if irq0_pin := config.get(CONF_IRQ0_PIN): + pin = await cg.gpio_pin_expression(irq0_pin) + cg.add(var.set_irq0_pin(pin)) + + pin = await cg.gpio_pin_expression(config[CONF_IRQ1_PIN]) + cg.add(var.set_irq1_pin(pin)) + + if reset_pin := config.get(CONF_RESET_PIN): + pin = await cg.gpio_pin_expression(reset_pin) + cg.add(var.set_reset_pin(pin)) + + if frequency := config.get(CONF_FREQUENCY): + cg.add(var.set_frequency(frequency)) + + if channel := config.get(CONF_PHASE_A): + chan = await power_channel(channel) + cg.add(var.set_channel_a(chan)) + + if channel := config.get(CONF_PHASE_B): + chan = await power_channel(channel) + cg.add(var.set_channel_b(chan)) + + if channel := config.get(CONF_PHASE_C): + chan = await power_channel(channel) + cg.add(var.set_channel_c(chan)) + + if channel := config.get(CONF_NEUTRAL): + chan = await neutral_channel(channel) + cg.add(var.set_channel_n(chan)) diff --git a/esphome/components/ade7953_base/__init__.py b/esphome/components/ade7953_base/__init__.py index d4c18f8ffd..28014ef142 100644 --- a/esphome/components/ade7953_base/__init__.py +++ b/esphome/components/ade7953_base/__init__.py @@ -41,6 +41,7 @@ CONF_CURRENT_GAIN_A = "current_gain_a" CONF_CURRENT_GAIN_B = "current_gain_b" CONF_ACTIVE_POWER_GAIN_A = "active_power_gain_a" CONF_ACTIVE_POWER_GAIN_B = "active_power_gain_b" +CONF_USE_ACCUMULATED_ENERGY_REGISTERS = "use_accumulated_energy_registers" PGA_GAINS = { "1x": 0b000, "2x": 0b001, @@ -155,6 +156,7 @@ ADE7953_CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_ACTIVE_POWER_GAIN_B, default=0x400000): cv.hex_int_range( min=0x100000, max=0x800000 ), + cv.Optional(CONF_USE_ACCUMULATED_ENERGY_REGISTERS, default=False): cv.boolean, } ).extend(cv.polling_component_schema("60s")) @@ -174,6 +176,9 @@ async def register_ade7953(var, config): cg.add(var.set_bigain(config.get(CONF_CURRENT_GAIN_B))) cg.add(var.set_awgain(config.get(CONF_ACTIVE_POWER_GAIN_A))) cg.add(var.set_bwgain(config.get(CONF_ACTIVE_POWER_GAIN_B))) + cg.add( + var.set_use_acc_energy_regs(config.get(CONF_USE_ACCUMULATED_ENERGY_REGISTERS)) + ) for key in [ CONF_VOLTAGE, diff --git a/esphome/components/ade7953_base/ade7953_base.cpp b/esphome/components/ade7953_base/ade7953_base.cpp index af6fe0a5df..862f5567a8 100644 --- a/esphome/components/ade7953_base/ade7953_base.cpp +++ b/esphome/components/ade7953_base/ade7953_base.cpp @@ -6,6 +6,9 @@ namespace ade7953_base { static const char *const TAG = "ade7953"; +static const float ADE_POWER_FACTOR = 154.0f; +static const float ADE_WATTSEC_POWER_FACTOR = ADE_POWER_FACTOR * ADE_POWER_FACTOR / 3600; + void ADE7953::setup() { if (this->irq_pin_ != nullptr) { this->irq_pin_->setup(); @@ -34,6 +37,7 @@ void ADE7953::setup() { this->ade_read_32(BIGAIN_32, &bigain_); this->ade_read_32(AWGAIN_32, &awgain_); this->ade_read_32(BWGAIN_32, &bwgain_); + this->last_update_ = millis(); this->is_setup_ = true; }); } @@ -52,6 +56,7 @@ void ADE7953::dump_config() { LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_); LOG_SENSOR(" ", "Rective Power A Sensor", this->reactive_power_a_sensor_); LOG_SENSOR(" ", "Reactive Power B Sensor", this->reactive_power_b_sensor_); + ESP_LOGCONFIG(TAG, " USE_ACC_ENERGY_REGS: %d", this->use_acc_energy_regs_); ESP_LOGCONFIG(TAG, " PGA_V_8: 0x%X", pga_v_); ESP_LOGCONFIG(TAG, " PGA_IA_8: 0x%X", pga_ia_); ESP_LOGCONFIG(TAG, " PGA_IB_8: 0x%X", pga_ib_); @@ -85,6 +90,7 @@ void ADE7953::update() { uint32_t val; uint16_t val_16; + uint16_t reg; // Power factor err = this->ade_read_16(0x010A, &val_16); @@ -92,23 +98,36 @@ void ADE7953::update() { err = this->ade_read_16(0x010B, &val_16); ADE_PUBLISH(power_factor_b, (int16_t) val_16, (0x7FFF / 100.0f)); + float pf = ADE_POWER_FACTOR; + if (this->use_acc_energy_regs_) { + const uint32_t now = millis(); + const auto diff = now - this->last_update_; + this->last_update_ = now; + // prevent DIV/0 + pf = ADE_WATTSEC_POWER_FACTOR * (diff < 10 ? 10 : diff) / 1000; + ESP_LOGVV(TAG, "ADE7953::update() diff=%d pf=%f", diff, pf); + } + // Apparent power - err = this->ade_read_32(0x0310, &val); - ADE_PUBLISH(apparent_power_a, (int32_t) val, 154.0f); - err = this->ade_read_32(0x0311, &val); - ADE_PUBLISH(apparent_power_b, (int32_t) val, 154.0f); + reg = this->use_acc_energy_regs_ ? 0x0322 : 0x0310; + err = this->ade_read_32(reg, &val); + ADE_PUBLISH(apparent_power_a, (int32_t) val, pf); + err = this->ade_read_32(reg + 1, &val); + ADE_PUBLISH(apparent_power_b, (int32_t) val, pf); // Active power - err = this->ade_read_32(0x0312, &val); - ADE_PUBLISH(active_power_a, (int32_t) val, 154.0f); - err = this->ade_read_32(0x0313, &val); - ADE_PUBLISH(active_power_b, (int32_t) val, 154.0f); + reg = this->use_acc_energy_regs_ ? 0x031E : 0x0312; + err = this->ade_read_32(reg, &val); + ADE_PUBLISH(active_power_a, (int32_t) val, pf); + err = this->ade_read_32(reg + 1, &val); + ADE_PUBLISH(active_power_b, (int32_t) val, pf); // Reactive power - err = this->ade_read_32(0x0314, &val); - ADE_PUBLISH(reactive_power_a, (int32_t) val, 154.0f); - err = this->ade_read_32(0x0315, &val); - ADE_PUBLISH(reactive_power_b, (int32_t) val, 154.0f); + reg = this->use_acc_energy_regs_ ? 0x0320 : 0x0314; + err = this->ade_read_32(reg, &val); + ADE_PUBLISH(reactive_power_a, (int32_t) val, pf); + err = this->ade_read_32(reg + 1, &val); + ADE_PUBLISH(reactive_power_b, (int32_t) val, pf); // Current err = this->ade_read_32(0x031A, &val); diff --git a/esphome/components/ade7953_base/ade7953_base.h b/esphome/components/ade7953_base/ade7953_base.h index f57a8aa1df..d711a5c6be 100644 --- a/esphome/components/ade7953_base/ade7953_base.h +++ b/esphome/components/ade7953_base/ade7953_base.h @@ -52,6 +52,8 @@ class ADE7953 : public PollingComponent, public sensor::Sensor { void set_awgain(uint32_t awgain) { awgain_ = awgain; } void set_bwgain(uint32_t bwgain) { bwgain_ = bwgain; } + void set_use_acc_energy_regs(bool use_acc_energy_regs) { use_acc_energy_regs_ = use_acc_energy_regs; } + void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; } @@ -103,6 +105,8 @@ class ADE7953 : public PollingComponent, public sensor::Sensor { uint32_t bigain_; uint32_t awgain_; uint32_t bwgain_; + bool use_acc_energy_regs_{false}; + uint32_t last_update_; virtual bool ade_write_8(uint16_t reg, uint8_t value) = 0; diff --git a/esphome/components/ade7953_i2c/ade7953_i2c.cpp b/esphome/components/ade7953_i2c/ade7953_i2c.cpp index 572337428a..ae381824db 100644 --- a/esphome/components/ade7953_i2c/ade7953_i2c.cpp +++ b/esphome/components/ade7953_i2c/ade7953_i2c.cpp @@ -13,29 +13,29 @@ void AdE7953I2c::dump_config() { ade7953_base::ADE7953::dump_config(); } bool AdE7953I2c::ade_write_8(uint16_t reg, uint8_t value) { - std::vector data(3); - data.push_back(reg >> 8); - data.push_back(reg >> 0); - data.push_back(value); - return this->write(data.data(), data.size()) != i2c::ERROR_OK; + uint8_t data[3]; + data[0] = reg >> 8; + data[1] = reg >> 0; + data[2] = value; + return this->write(data, 3) != i2c::ERROR_OK; } bool AdE7953I2c::ade_write_16(uint16_t reg, uint16_t value) { - std::vector data(4); - data.push_back(reg >> 8); - data.push_back(reg >> 0); - data.push_back(value >> 8); - data.push_back(value >> 0); - return this->write(data.data(), data.size()) != i2c::ERROR_OK; + uint8_t data[4]; + data[0] = reg >> 8; + data[1] = reg >> 0; + data[2] = value >> 8; + data[3] = value >> 0; + return this->write(data, 4) != i2c::ERROR_OK; } bool AdE7953I2c::ade_write_32(uint16_t reg, uint32_t value) { - std::vector data(6); - data.push_back(reg >> 8); - data.push_back(reg >> 0); - data.push_back(value >> 24); - data.push_back(value >> 16); - data.push_back(value >> 8); - data.push_back(value >> 0); - return this->write(data.data(), data.size()) != i2c::ERROR_OK; + uint8_t data[6]; + data[0] = reg >> 8; + data[1] = reg >> 0; + data[2] = value >> 24; + data[3] = value >> 16; + data[4] = value >> 8; + data[5] = value >> 0; + return this->write(data, 6) != i2c::ERROR_OK; } bool AdE7953I2c::ade_read_8(uint16_t reg, uint8_t *value) { uint8_t reg_data[2]; diff --git a/esphome/components/ads1118/__init__.py b/esphome/components/ads1118/__init__.py new file mode 100644 index 0000000000..f8d51101a6 --- /dev/null +++ b/esphome/components/ads1118/__init__.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import spi +from esphome.const import CONF_ID + +CODEOWNERS = ["@solomondg1"] +DEPENDENCIES = ["spi"] +MULTI_CONF = True + +CONF_ADS1118_ID = "ads1118_id" + +ads1118_ns = cg.esphome_ns.namespace("ads1118") +ADS1118 = ads1118_ns.class_("ADS1118", cg.Component, spi.SPIDevice) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(ADS1118), + } +).extend(spi.spi_device_schema(cs_pin_required=True)) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) diff --git a/esphome/components/ads1118/ads1118.cpp b/esphome/components/ads1118/ads1118.cpp new file mode 100644 index 0000000000..7b9d0cc36b --- /dev/null +++ b/esphome/components/ads1118/ads1118.cpp @@ -0,0 +1,126 @@ +#include "ads1118.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ads1118 { + +static const char *const TAG = "ads1118"; +static const uint8_t ADS1118_DATA_RATE_860_SPS = 0b111; + +void ADS1118::setup() { + ESP_LOGCONFIG(TAG, "Setting up ads1118"); + this->spi_setup(); + + this->config_ = 0; + // Setup multiplexer + // 0bx000xxxxxxxxxxxx + this->config_ |= ADS1118_MULTIPLEXER_P0_NG << 12; + + // Setup Gain + // 0bxxxx000xxxxxxxxx + this->config_ |= ADS1118_GAIN_6P144 << 9; + + // Set singleshot mode + // 0bxxxxxxx1xxxxxxxx + this->config_ |= 0b0000000100000000; + + // Set data rate - 860 samples per second (we're in singleshot mode) + // 0bxxxxxxxx100xxxxx + this->config_ |= ADS1118_DATA_RATE_860_SPS << 5; + + // Set temperature sensor mode - ADC + // 0bxxxxxxxxxxx0xxxx + this->config_ |= 0b0000000000000000; + + // Set DOUT pull up - enable + // 0bxxxxxxxxxxxx0xxx + this->config_ |= 0b0000000000001000; + + // NOP - must be 01 + // 0bxxxxxxxxxxxxx01x + this->config_ |= 0b0000000000000010; + + // Not used - can be 0 or 1, lets be positive + // 0bxxxxxxxxxxxxxxx1 + this->config_ |= 0b0000000000000001; +} + +void ADS1118::dump_config() { + ESP_LOGCONFIG(TAG, "ADS1118:"); + LOG_PIN(" CS Pin:", this->cs_); +} + +float ADS1118::request_measurement(ADS1118Multiplexer multiplexer, ADS1118Gain gain, bool temperature_mode) { + uint16_t temp_config = this->config_; + // Multiplexer + // 0bxBBBxxxxxxxxxxxx + temp_config &= 0b1000111111111111; + temp_config |= (multiplexer & 0b111) << 12; + + // Gain + // 0bxxxxBBBxxxxxxxxx + temp_config &= 0b1111000111111111; + temp_config |= (gain & 0b111) << 9; + + if (temperature_mode) { + // Set temperature sensor mode + // 0bxxxxxxxxxxx1xxxx + temp_config |= 0b0000000000010000; + } else { + // Set ADC mode + // 0bxxxxxxxxxxx0xxxx + temp_config &= 0b1111111111101111; + } + + // Start conversion + temp_config |= 0b1000000000000000; + + this->enable(); + this->write_byte16(temp_config); + this->disable(); + + // about 1.2 ms with 860 samples per second + delay(2); + + this->enable(); + uint8_t adc_first_byte = this->read_byte(); + uint8_t adc_second_byte = this->read_byte(); + this->disable(); + uint16_t raw_conversion = encode_uint16(adc_first_byte, adc_second_byte); + + auto signed_conversion = static_cast(raw_conversion); + + if (temperature_mode) { + return (signed_conversion >> 2) * 0.03125f; + } else { + float millivolts; + float divider = 32768.0f; + switch (gain) { + case ADS1118_GAIN_6P144: + millivolts = (signed_conversion * 6144) / divider; + break; + case ADS1118_GAIN_4P096: + millivolts = (signed_conversion * 4096) / divider; + break; + case ADS1118_GAIN_2P048: + millivolts = (signed_conversion * 2048) / divider; + break; + case ADS1118_GAIN_1P024: + millivolts = (signed_conversion * 1024) / divider; + break; + case ADS1118_GAIN_0P512: + millivolts = (signed_conversion * 512) / divider; + break; + case ADS1118_GAIN_0P256: + millivolts = (signed_conversion * 256) / divider; + break; + default: + millivolts = NAN; + } + + return millivolts / 1e3f; + } +} + +} // namespace ads1118 +} // namespace esphome diff --git a/esphome/components/ads1118/ads1118.h b/esphome/components/ads1118/ads1118.h new file mode 100644 index 0000000000..8b9aa15cd2 --- /dev/null +++ b/esphome/components/ads1118/ads1118.h @@ -0,0 +1,46 @@ +#pragma once + +#include "esphome/components/spi/spi.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace ads1118 { + +enum ADS1118Multiplexer { + ADS1118_MULTIPLEXER_P0_N1 = 0b000, + ADS1118_MULTIPLEXER_P0_N3 = 0b001, + ADS1118_MULTIPLEXER_P1_N3 = 0b010, + ADS1118_MULTIPLEXER_P2_N3 = 0b011, + ADS1118_MULTIPLEXER_P0_NG = 0b100, + ADS1118_MULTIPLEXER_P1_NG = 0b101, + ADS1118_MULTIPLEXER_P2_NG = 0b110, + ADS1118_MULTIPLEXER_P3_NG = 0b111, +}; + +enum ADS1118Gain { + ADS1118_GAIN_6P144 = 0b000, + ADS1118_GAIN_4P096 = 0b001, + ADS1118_GAIN_2P048 = 0b010, + ADS1118_GAIN_1P024 = 0b011, + ADS1118_GAIN_0P512 = 0b100, + ADS1118_GAIN_0P256 = 0b101, +}; + +class ADS1118 : public Component, + public spi::SPIDevice { + public: + ADS1118() = default; + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + /// Helper method to request a measurement from a sensor. + float request_measurement(ADS1118Multiplexer multiplexer, ADS1118Gain gain, bool temperature_mode); + + protected: + uint16_t config_{0}; +}; + +} // namespace ads1118 +} // namespace esphome diff --git a/esphome/components/ads1118/sensor/__init__.py b/esphome/components/ads1118/sensor/__init__.py new file mode 100644 index 0000000000..4e89115447 --- /dev/null +++ b/esphome/components/ads1118/sensor/__init__.py @@ -0,0 +1,97 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, voltage_sampler +from esphome.const import ( + CONF_GAIN, + CONF_MULTIPLEXER, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_VOLT, + CONF_TYPE, +) +from .. import ads1118_ns, ADS1118, CONF_ADS1118_ID + +AUTO_LOAD = ["voltage_sampler"] +DEPENDENCIES = ["ads1118"] + +ADS1118Multiplexer = ads1118_ns.enum("ADS1118Multiplexer") +MUX = { + "A0_A1": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P0_N1, + "A0_A3": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P0_N3, + "A1_A3": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P1_N3, + "A2_A3": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P2_N3, + "A0_GND": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P0_NG, + "A1_GND": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P1_NG, + "A2_GND": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P2_NG, + "A3_GND": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P3_NG, +} + +ADS1118Gain = ads1118_ns.enum("ADS1118Gain") +GAIN = { + "6.144": ADS1118Gain.ADS1118_GAIN_6P144, + "4.096": ADS1118Gain.ADS1118_GAIN_4P096, + "2.048": ADS1118Gain.ADS1118_GAIN_2P048, + "1.024": ADS1118Gain.ADS1118_GAIN_1P024, + "0.512": ADS1118Gain.ADS1118_GAIN_0P512, + "0.256": ADS1118Gain.ADS1118_GAIN_0P256, +} + + +ADS1118Sensor = ads1118_ns.class_( + "ADS1118Sensor", + cg.PollingComponent, + sensor.Sensor, + voltage_sampler.VoltageSampler, + cg.Parented.template(ADS1118), +) + +TYPE_ADC = "adc" +TYPE_TEMPERATURE = "temperature" + +CONFIG_SCHEMA = cv.typed_schema( + { + TYPE_ADC: sensor.sensor_schema( + ADS1118Sensor, + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.GenerateID(CONF_ADS1118_ID): cv.use_id(ADS1118), + cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"), + cv.Required(CONF_GAIN): cv.enum(GAIN, string=True), + } + ) + .extend(cv.polling_component_schema("60s")), + TYPE_TEMPERATURE: sensor.sensor_schema( + ADS1118Sensor, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.GenerateID(CONF_ADS1118_ID): cv.use_id(ADS1118), + } + ) + .extend(cv.polling_component_schema("60s")), + }, + default_type=TYPE_ADC, +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await cg.register_parented(var, config[CONF_ADS1118_ID]) + + if config[CONF_TYPE] == TYPE_ADC: + cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER])) + cg.add(var.set_gain(config[CONF_GAIN])) + if config[CONF_TYPE] == TYPE_TEMPERATURE: + cg.add(var.set_temperature_mode(True)) diff --git a/esphome/components/ads1118/sensor/ads1118_sensor.cpp b/esphome/components/ads1118/sensor/ads1118_sensor.cpp new file mode 100644 index 0000000000..c3ce3bdc9c --- /dev/null +++ b/esphome/components/ads1118/sensor/ads1118_sensor.cpp @@ -0,0 +1,29 @@ +#include "ads1118_sensor.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace ads1118 { + +static const char *const TAG = "ads1118.sensor"; + +void ADS1118Sensor::dump_config() { + LOG_SENSOR(" ", "ADS1118 Sensor", this); + ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_); + ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_); +} + +float ADS1118Sensor::sample() { + return this->parent_->request_measurement(this->multiplexer_, this->gain_, this->temperature_mode_); +} + +void ADS1118Sensor::update() { + float v = this->sample(); + if (!std::isnan(v)) { + ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v); + this->publish_state(v); + } +} + +} // namespace ads1118 +} // namespace esphome diff --git a/esphome/components/ads1118/sensor/ads1118_sensor.h b/esphome/components/ads1118/sensor/ads1118_sensor.h new file mode 100644 index 0000000000..d2d7a03f59 --- /dev/null +++ b/esphome/components/ads1118/sensor/ads1118_sensor.h @@ -0,0 +1,36 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/voltage_sampler/voltage_sampler.h" + +#include "../ads1118.h" + +namespace esphome { +namespace ads1118 { + +class ADS1118Sensor : public PollingComponent, + public sensor::Sensor, + public voltage_sampler::VoltageSampler, + public Parented { + public: + void update() override; + + void set_multiplexer(ADS1118Multiplexer multiplexer) { this->multiplexer_ = multiplexer; } + void set_gain(ADS1118Gain gain) { this->gain_ = gain; } + void set_temperature_mode(bool temp) { this->temperature_mode_ = temp; } + + float sample() override; + + void dump_config() override; + + protected: + ADS1118Multiplexer multiplexer_{ADS1118_MULTIPLEXER_P0_NG}; + ADS1118Gain gain_{ADS1118_GAIN_6P144}; + bool temperature_mode_; +}; + +} // namespace ads1118 +} // namespace esphome diff --git a/esphome/components/ags10/__init__.py b/esphome/components/ags10/__init__.py new file mode 100644 index 0000000000..37f34d06df --- /dev/null +++ b/esphome/components/ags10/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@mak-42"] diff --git a/esphome/components/ags10/ags10.cpp b/esphome/components/ags10/ags10.cpp new file mode 100644 index 0000000000..dfaa00e2e9 --- /dev/null +++ b/esphome/components/ags10/ags10.cpp @@ -0,0 +1,212 @@ +#include "ags10.h" + +namespace esphome { +namespace ags10 { +static const char *const TAG = "ags10"; + +// Data acquisition. +static const uint8_t REG_TVOC = 0x00; +// Zero-point calibration. +static const uint8_t REG_CALIBRATION = 0x01; +// Read version. +static const uint8_t REG_VERSION = 0x11; +// Read current resistance. +static const uint8_t REG_RESISTANCE = 0x20; +// Modify target address. +static const uint8_t REG_ADDRESS = 0x21; + +// Zero-point calibration with current resistance. +static const uint16_t ZP_CURRENT = 0x0000; +// Zero-point reset. +static const uint16_t ZP_DEFAULT = 0xFFFF; + +void AGS10Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up ags10..."); + + auto version = this->read_version_(); + if (version) { + ESP_LOGD(TAG, "AGS10 Sensor Version: 0x%02X", *version); + if (this->version_ != nullptr) { + this->version_->publish_state(*version); + } + } else { + ESP_LOGE(TAG, "AGS10 Sensor Version: unknown"); + } + + auto resistance = this->read_resistance_(); + if (resistance) { + ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08X", *resistance); + if (this->resistance_ != nullptr) { + this->resistance_->publish_state(*resistance); + } + } else { + ESP_LOGE(TAG, "AGS10 Sensor Resistance: unknown"); + } + + ESP_LOGD(TAG, "Sensor initialized"); +} + +void AGS10Component::update() { + auto tvoc = this->read_tvoc_(); + if (tvoc) { + this->tvoc_->publish_state(*tvoc); + this->status_clear_warning(); + } else { + this->status_set_warning(); + } +} + +void AGS10Component::dump_config() { + ESP_LOGCONFIG(TAG, "AGS10:"); + LOG_I2C_DEVICE(this); + switch (this->error_code_) { + case NONE: + break; + case COMMUNICATION_FAILED: + ESP_LOGE(TAG, "Communication with AGS10 failed!"); + break; + case CRC_CHECK_FAILED: + ESP_LOGE(TAG, "The crc check failed"); + break; + case ILLEGAL_STATUS: + ESP_LOGE(TAG, "AGS10 is not ready to return TVOC data or sensor in pre-heat stage."); + break; + case UNSUPPORTED_UNITS: + ESP_LOGE(TAG, "AGS10 returns TVOC data in unsupported units."); + break; + default: + ESP_LOGE(TAG, "Unknown error: %d", this->error_code_); + break; + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_); + LOG_SENSOR(" ", "Firmware Version Sensor", this->version_); + LOG_SENSOR(" ", "Resistance Sensor", this->resistance_); +} + +/** + * Sets new I2C address of AGS10. + */ +bool AGS10Component::new_i2c_address(uint8_t newaddress) { + uint8_t rev_newaddress = ~newaddress; + std::array data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0}; + data[4] = calc_crc8_(data, 4); + if (!this->write_bytes(REG_ADDRESS, data)) { + this->error_code_ = COMMUNICATION_FAILED; + this->status_set_warning(); + ESP_LOGE(TAG, "couldn't write the new I2C address 0x%02X", newaddress); + return false; + } + this->set_i2c_address(newaddress); + ESP_LOGW(TAG, "changed I2C address to 0x%02X", newaddress); + this->error_code_ = NONE; + this->status_clear_warning(); + return true; +} + +bool AGS10Component::set_zero_point_with_factory_defaults() { return this->set_zero_point_with(ZP_DEFAULT); } + +bool AGS10Component::set_zero_point_with_current_resistance() { return this->set_zero_point_with(ZP_CURRENT); } + +bool AGS10Component::set_zero_point_with(uint16_t value) { + std::array data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0}; + data[4] = calc_crc8_(data, 4); + if (!this->write_bytes(REG_CALIBRATION, data)) { + this->error_code_ = COMMUNICATION_FAILED; + this->status_set_warning(); + ESP_LOGE(TAG, "unable to set zero-point calibration with 0x%02X", value); + return false; + } + if (value == ZP_CURRENT) { + ESP_LOGI(TAG, "zero-point calibration has been set with current resistance"); + } else if (value == ZP_DEFAULT) { + ESP_LOGI(TAG, "zero-point calibration has been reset to the factory defaults"); + } else { + ESP_LOGI(TAG, "zero-point calibration has been set with 0x%02X", value); + } + this->error_code_ = NONE; + this->status_clear_warning(); + return true; +} + +optional AGS10Component::read_tvoc_() { + auto data = this->read_and_check_<5>(REG_TVOC); + if (!data) { + return nullopt; + } + + auto res = *data; + auto status_byte = res[0]; + + int units = status_byte & 0x0e; + int status_bit = status_byte & 0x01; + + if (status_bit != 0) { + this->error_code_ = ILLEGAL_STATUS; + ESP_LOGW(TAG, "Reading AGS10 data failed: illegal status (not ready or sensor in pre-heat stage)!"); + return nullopt; + } + + if (units != 0) { + this->error_code_ = UNSUPPORTED_UNITS; + ESP_LOGE(TAG, "Reading AGS10 data failed: unsupported units (%d)!", units); + return nullopt; + } + + return encode_uint24(res[1], res[2], res[3]); +} + +optional AGS10Component::read_version_() { + auto data = this->read_and_check_<5>(REG_VERSION); + if (data) { + auto res = *data; + return res[3]; + } + return nullopt; +} + +optional AGS10Component::read_resistance_() { + auto data = this->read_and_check_<5>(REG_RESISTANCE); + if (data) { + auto res = *data; + return encode_uint32(res[0], res[1], res[2], res[3]); + } + return nullopt; +} + +template optional> AGS10Component::read_and_check_(uint8_t a_register) { + auto data = this->read_bytes(a_register); + if (!data.has_value()) { + this->error_code_ = COMMUNICATION_FAILED; + ESP_LOGE(TAG, "Reading AGS10 version failed!"); + return optional>(); + } + auto len = N - 1; + auto res = *data; + auto crc_byte = res[len]; + + if (crc_byte != calc_crc8_(res, len)) { + this->error_code_ = CRC_CHECK_FAILED; + ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!"); + return optional>(); + } + + return data; +} + +template uint8_t AGS10Component::calc_crc8_(std::array dat, uint8_t num) { + uint8_t i, byte1, crc = 0xFF; + for (byte1 = 0; byte1 < num; byte1++) { + crc ^= (dat[byte1]); + for (i = 0; i < 8; i++) { + if (crc & 0x80) { + crc = (crc << 1) ^ 0x31; + } else { + crc = (crc << 1); + } + } + } + return crc; +} +} // namespace ags10 +} // namespace esphome diff --git a/esphome/components/ags10/ags10.h b/esphome/components/ags10/ags10.h new file mode 100644 index 0000000000..f2201fe70c --- /dev/null +++ b/esphome/components/ags10/ags10.h @@ -0,0 +1,152 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace ags10 { + +class AGS10Component : public PollingComponent, public i2c::I2CDevice { + public: + /** + * Sets TVOC sensor. + */ + void set_tvoc(sensor::Sensor *tvoc) { this->tvoc_ = tvoc; } + + /** + * Sets version info sensor. + */ + void set_version(sensor::Sensor *version) { this->version_ = version; } + + /** + * Sets resistance info sensor. + */ + void set_resistance(sensor::Sensor *resistance) { this->resistance_ = resistance; } + + void setup() override; + + void update() override; + + void dump_config() override; + + float get_setup_priority() const override { return setup_priority::DATA; } + + /** + * Modifies target address of AGS10. + * + * New address is saved and takes effect immediately even after power-off. + */ + bool new_i2c_address(uint8_t newaddress); + + /** + * Sets zero-point with factory defaults. + */ + bool set_zero_point_with_factory_defaults(); + + /** + * Sets zero-point with current sensor resistance. + */ + bool set_zero_point_with_current_resistance(); + + /** + * Sets zero-point with the value. + */ + bool set_zero_point_with(uint16_t value); + + protected: + /** + * TVOC. + */ + sensor::Sensor *tvoc_{nullptr}; + + /** + * Firmvare version. + */ + sensor::Sensor *version_{nullptr}; + + /** + * Resistance. + */ + sensor::Sensor *resistance_{nullptr}; + + /** + * Last operation error code. + */ + enum ErrorCode { + NONE = 0, + COMMUNICATION_FAILED, + CRC_CHECK_FAILED, + ILLEGAL_STATUS, + UNSUPPORTED_UNITS, + } error_code_{NONE}; + + /** + * Reads and returns value of TVOC. + */ + optional read_tvoc_(); + + /** + * Reads and returns a firmware version of AGS10. + */ + optional read_version_(); + + /** + * Reads and returns the resistance of AGS10. + */ + optional read_resistance_(); + + /** + * Read, checks and returns data from the sensor. + */ + template optional> read_and_check_(uint8_t a_register); + + /** + * Calculates CRC8 value. + * + * CRC8 calculation, initial value: 0xFF, polynomial: 0x31 (x8+ x5+ x4+1) + * + * @param[in] dat the data buffer + * @param num number of bytes in the buffer + */ + template uint8_t calc_crc8_(std::array dat, uint8_t num); +}; + +template class AGS10NewI2cAddressAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(uint8_t, new_address) + + void play(Ts... x) override { this->parent_->new_i2c_address(this->new_address_.value(x...)); } +}; + +enum AGS10SetZeroPointActionMode { + // Zero-point reset. + FACTORY_DEFAULT, + // Zero-point calibration with current resistance. + CURRENT_VALUE, + // Zero-point calibration with custom resistance. + CUSTOM_VALUE, +}; + +template class AGS10SetZeroPointAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(uint16_t, value) + TEMPLATABLE_VALUE(AGS10SetZeroPointActionMode, mode) + + void play(Ts... x) override { + switch (this->mode_.value(x...)) { + case FACTORY_DEFAULT: + this->parent_->set_zero_point_with_factory_defaults(); + break; + case CURRENT_VALUE: + this->parent_->set_zero_point_with_current_resistance(); + break; + case CUSTOM_VALUE: + this->parent_->set_zero_point_with(this->value_.value(x...)); + break; + } + } +}; +} // namespace ags10 +} // namespace esphome diff --git a/esphome/components/ags10/sensor.py b/esphome/components/ags10/sensor.py new file mode 100644 index 0000000000..59aebd636b --- /dev/null +++ b/esphome/components/ags10/sensor.py @@ -0,0 +1,132 @@ +import esphome.codegen as cg +from esphome import automation +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + ICON_RADIATOR, + ICON_RESTART, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, + ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_MEASUREMENT, + UNIT_OHM, + UNIT_PARTS_PER_BILLION, + CONF_ADDRESS, + CONF_TVOC, + CONF_VERSION, + CONF_MODE, + CONF_VALUE, +) + +CONF_RESISTANCE = "resistance" + +DEPENDENCIES = ["i2c"] + +ags10_ns = cg.esphome_ns.namespace("ags10") +AGS10Component = ags10_ns.class_("AGS10Component", cg.PollingComponent, i2c.I2CDevice) + +# Actions +AGS10NewI2cAddressAction = ags10_ns.class_( + "AGS10NewI2cAddressAction", automation.Action +) +AGS10SetZeroPointAction = ags10_ns.class_("AGS10SetZeroPointAction", automation.Action) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(AGS10Component), + cv.Optional(CONF_TVOC): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_BILLION, + icon=ICON_RADIATOR, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_VERSION): sensor.sensor_schema( + icon=ICON_RESTART, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_RESISTANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_OHM, + icon=ICON_RESTART, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x1A)) +) + +FINAL_VALIDATE_SCHEMA = i2c.final_validate_device_schema("ags10", max_frequency="15khz") + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + sens = await sensor.new_sensor(config[CONF_TVOC]) + cg.add(var.set_tvoc(sens)) + + if version_config := config.get(CONF_VERSION): + sens = await sensor.new_sensor(version_config) + cg.add(var.set_version(sens)) + + if resistance_config := config.get(CONF_RESISTANCE): + sens = await sensor.new_sensor(resistance_config) + cg.add(var.set_resistance(sens)) + + +AGS10_NEW_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(AGS10Component), + cv.Required(CONF_ADDRESS): cv.templatable(cv.i2c_address), + }, + key=CONF_ADDRESS, +) + + +@automation.register_action( + "ags10.new_i2c_address", + AGS10NewI2cAddressAction, + AGS10_NEW_I2C_ADDRESS_SCHEMA, +) +async def ags10newi2caddress_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + address = await cg.templatable(config[CONF_ADDRESS], args, int) + cg.add(var.set_new_address(address)) + return var + + +AGS10SetZeroPointActionMode = ags10_ns.enum("AGS10SetZeroPointActionMode") +AGS10_SET_ZERO_POINT_ACTION_MODE = { + "FACTORY_DEFAULT": AGS10SetZeroPointActionMode.FACTORY_DEFAULT, + "CURRENT_VALUE": AGS10SetZeroPointActionMode.CURRENT_VALUE, + "CUSTOM_VALUE": AGS10SetZeroPointActionMode.CUSTOM_VALUE, +} + +AGS10_SET_ZERO_POINT_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(AGS10Component), + cv.Required(CONF_MODE): cv.enum(AGS10_SET_ZERO_POINT_ACTION_MODE, upper=True), + cv.Optional(CONF_VALUE, default=0xFFFF): cv.templatable(cv.uint16_t), + }, +) + + +@automation.register_action( + "ags10.set_zero_point", + AGS10SetZeroPointAction, + AGS10_SET_ZERO_POINT_SCHEMA, +) +async def ags10setzeropoint_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + mode = await cg.templatable(config.get(CONF_MODE), args, enumerate) + cg.add(var.set_mode(mode)) + value = await cg.templatable(config[CONF_VALUE], args, int) + cg.add(var.set_value(value)) + return var diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index 4d69a67487..332218b9e9 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -15,36 +15,43 @@ #include "aht10.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" -#include namespace esphome { namespace aht10 { static const char *const TAG = "aht10"; -static const size_t SIZE_CALIBRATE_CMD = 3; -static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1, 0x08, 0x00}; -static const uint8_t AHT20_CALIBRATE_CMD[] = {0xBE, 0x08, 0x00}; +static const uint8_t AHT10_INITIALIZE_CMD[] = {0xE1, 0x08, 0x00}; +static const uint8_t AHT20_INITIALIZE_CMD[] = {0xBE, 0x08, 0x00}; static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00}; -static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for calibration and temperature measurement -static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms -static const uint8_t AHT10_ATTEMPTS = 3; // safety margin, normally 3 attempts are enough: 3*30=90ms -static const uint8_t AHT10_CAL_ATTEMPTS = 10; +static const uint8_t AHT10_SOFTRESET_CMD[] = {0xBA}; + +static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for initialization and temperature measurement +static const uint8_t AHT10_READ_DELAY = 80; // ms, time to wait for conversion result +static const uint8_t AHT10_SOFTRESET_DELAY = 30; // ms + +static const uint8_t AHT10_ATTEMPTS = 3; // safety margin, normally 3 attempts are enough: 3*30=90ms +static const uint8_t AHT10_INIT_ATTEMPTS = 10; + static const uint8_t AHT10_STATUS_BUSY = 0x80; void AHT10Component::setup() { - const uint8_t *calibrate_cmd; + if (this->write(AHT10_SOFTRESET_CMD, sizeof(AHT10_SOFTRESET_CMD)) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Reset AHT10 failed!"); + } + delay(AHT10_SOFTRESET_DELAY); + + i2c::ErrorCode error_code = i2c::ERROR_INVALID_ARGUMENT; switch (this->variant_) { case AHT10Variant::AHT20: - calibrate_cmd = AHT20_CALIBRATE_CMD; ESP_LOGCONFIG(TAG, "Setting up AHT20"); + error_code = this->write(AHT20_INITIALIZE_CMD, sizeof(AHT20_INITIALIZE_CMD)); break; case AHT10Variant::AHT10: - default: - calibrate_cmd = AHT10_CALIBRATE_CMD; ESP_LOGCONFIG(TAG, "Setting up AHT10"); + error_code = this->write(AHT10_INITIALIZE_CMD, sizeof(AHT10_INITIALIZE_CMD)); + break; } - - if (this->write(calibrate_cmd, SIZE_CALIBRATE_CMD) != i2c::ERROR_OK) { + if (error_code != i2c::ERROR_OK) { ESP_LOGE(TAG, "Communication with AHT10 failed!"); this->mark_failed(); return; @@ -59,89 +66,92 @@ void AHT10Component::setup() { return; } ++cal_attempts; - if (cal_attempts > AHT10_CAL_ATTEMPTS) { - ESP_LOGE(TAG, "AHT10 calibration timed out!"); + if (cal_attempts > AHT10_INIT_ATTEMPTS) { + ESP_LOGE(TAG, "AHT10 initialization timed out!"); this->mark_failed(); return; } } if ((data & 0x68) != 0x08) { // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED - ESP_LOGE(TAG, "AHT10 calibration failed!"); + ESP_LOGE(TAG, "AHT10 initialization failed!"); this->mark_failed(); return; } - ESP_LOGV(TAG, "AHT10 calibrated"); + ESP_LOGV(TAG, "AHT10 initialization"); } -void AHT10Component::update() { - if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Communication with AHT10 failed!"); - this->status_set_warning(); +void AHT10Component::restart_read_() { + if (this->read_count_ == AHT10_ATTEMPTS) { + this->read_count_ = 0; + this->status_set_error("Measurements reading timed-out!"); return; } + this->read_count_++; + this->set_timeout(AHT10_READ_DELAY, [this]() { this->read_data_(); }); +} + +void AHT10Component::read_data_() { uint8_t data[6]; - uint8_t delay_ms = AHT10_DEFAULT_DELAY; - if (this->humidity_sensor_ != nullptr) - delay_ms = AHT10_HUMIDITY_DELAY; - bool success = false; - for (int i = 0; i < AHT10_ATTEMPTS; ++i) { - ESP_LOGVV(TAG, "Attempt %d at %6" PRIu32, i, millis()); - delay(delay_ms); - if (this->read(data, 6) != i2c::ERROR_OK) { - ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); - continue; - } - - if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy - ESP_LOGD(TAG, "AHT10 is busy, waiting..."); - } else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) { - // Unrealistic humidity (0x0) - if (this->humidity_sensor_ == nullptr) { - ESP_LOGVV(TAG, "ATH10 Unrealistic humidity (0x0), but humidity is not required"); - break; - } else { - ESP_LOGD(TAG, "ATH10 Unrealistic humidity (0x0), retrying..."); - if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Communication with AHT10 failed!"); - this->status_set_warning(); - return; - } - } - } else { - // data is valid, we can break the loop - ESP_LOGVV(TAG, "Answer at %6" PRIu32, millis()); - success = true; - break; - } - } - if (!success || (data[0] & 0x80) == 0x80) { - ESP_LOGE(TAG, "Measurements reading timed-out!"); - this->status_set_warning(); + if (this->read_count_ > 1) + ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_)); + if (this->read(data, 6) != i2c::ERROR_OK) { + this->status_set_warning("AHT10 read failed, retrying soon"); + this->restart_read_(); return; } + if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy + ESP_LOGD(TAG, "AHT10 is busy, waiting..."); + this->restart_read_(); + return; + } + if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) { + // Unrealistic humidity (0x0) + if (this->humidity_sensor_ == nullptr) { + ESP_LOGV(TAG, "ATH10 Unrealistic humidity (0x0), but humidity is not required"); + } else { + ESP_LOGD(TAG, "ATH10 Unrealistic humidity (0x0), retrying..."); + if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) { + this->status_set_warning("Communication with AHT10 failed!"); + } + this->restart_read_(); + return; + } + } + if (this->read_count_ > 1) + ESP_LOGD(TAG, "Success at %ums", (unsigned) (millis() - this->start_time_)); uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; - float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f; - float humidity; - if (raw_humidity == 0) { // unrealistic value - humidity = NAN; - } else { - humidity = (float) raw_humidity * 100.0f / 1048576.0f; - } - if (this->temperature_sensor_ != nullptr) { + float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f; this->temperature_sensor_->publish_state(temperature); } if (this->humidity_sensor_ != nullptr) { + float humidity; + if (raw_humidity == 0) { // unrealistic value + humidity = NAN; + } else { + humidity = (float) raw_humidity * 100.0f / 1048576.0f; + } if (std::isnan(humidity)) { ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum"); } this->humidity_sensor_->publish_state(humidity); } this->status_clear_warning(); + this->read_count_ = 0; +} +void AHT10Component::update() { + if (this->read_count_ != 0) + return; + this->start_time_ = millis(); + if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) { + this->status_set_warning("Communication with AHT10 failed!"); + return; + } + this->restart_read_(); } float AHT10Component::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/aht10/aht10.h b/esphome/components/aht10/aht10.h index 3840609d56..a3320c77e0 100644 --- a/esphome/components/aht10/aht10.h +++ b/esphome/components/aht10/aht10.h @@ -26,6 +26,10 @@ class AHT10Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; AHT10Variant variant_{}; + unsigned read_count_{}; + void read_data_(); + void restart_read_(); + uint32_t start_time_{}; }; } // namespace aht10 diff --git a/esphome/components/alarm_control_panel/__init__.py b/esphome/components/alarm_control_panel/__init__.py index d9cafb4f30..35d239c267 100644 --- a/esphome/components/alarm_control_panel/__init__.py +++ b/esphome/components/alarm_control_panel/__init__.py @@ -11,7 +11,7 @@ from esphome.const import ( ) from esphome.cpp_helpers import setup_entity -CODEOWNERS = ["@grahambrown11"] +CODEOWNERS = ["@grahambrown11", "@hwstar"] IS_PLATFORM_COMPONENT = True CONF_ON_TRIGGERED = "on_triggered" @@ -22,6 +22,8 @@ CONF_ON_ARMED_HOME = "on_armed_home" CONF_ON_ARMED_NIGHT = "on_armed_night" CONF_ON_ARMED_AWAY = "on_armed_away" CONF_ON_DISARMED = "on_disarmed" +CONF_ON_CHIME = "on_chime" +CONF_ON_READY = "on_ready" alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel") AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase) @@ -53,12 +55,22 @@ ArmedAwayTrigger = alarm_control_panel_ns.class_( DisarmedTrigger = alarm_control_panel_ns.class_( "DisarmedTrigger", automation.Trigger.template() ) +ChimeTrigger = alarm_control_panel_ns.class_( + "ChimeTrigger", automation.Trigger.template() +) +ReadyTrigger = alarm_control_panel_ns.class_( + "ReadyTrigger", automation.Trigger.template() +) + ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action) ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action) ArmNightAction = alarm_control_panel_ns.class_("ArmNightAction", automation.Action) DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action) PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action) TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action) +ChimeAction = alarm_control_panel_ns.class_("ChimeAction", automation.Action) +ReadyAction = alarm_control_panel_ns.class_("ReadyAction", automation.Action) + AlarmControlPanelCondition = alarm_control_panel_ns.class_( "AlarmControlPanelCondition", automation.Condition ) @@ -111,6 +123,16 @@ ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger), } ), + cv.Optional(CONF_ON_CHIME): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger), + } + ), + cv.Optional(CONF_ON_READY): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger), + } + ), } ) @@ -157,6 +179,12 @@ async def setup_alarm_control_panel_core_(var, config): for conf in config.get(CONF_ON_CLEARED, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_CHIME, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_READY, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) async def register_alarm_control_panel(var, config): @@ -232,6 +260,29 @@ async def alarm_action_trigger_to_code(config, action_id, template_arg, args): return var +@automation.register_action( + "alarm_control_panel.chime", ChimeAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA +) +async def alarm_action_chime_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + return var + + +@automation.register_action( + "alarm_control_panel.ready", ReadyAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA +) +@automation.register_condition( + "alarm_control_panel.ready", + AlarmControlPanelCondition, + ALARM_CONTROL_PANEL_CONDITION_SCHEMA, +) +async def alarm_action_ready_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + return var + + @automation.register_condition( "alarm_control_panel.is_armed", AlarmControlPanelCondition, diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index 9dc083c004..9f1485ee90 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -96,6 +96,14 @@ void AlarmControlPanel::add_on_cleared_callback(std::function &&callback this->cleared_callback_.add(std::move(callback)); } +void AlarmControlPanel::add_on_chime_callback(std::function &&callback) { + this->chime_callback_.add(std::move(callback)); +} + +void AlarmControlPanel::add_on_ready_callback(std::function &&callback) { + this->ready_callback_.add(std::move(callback)); +} + void AlarmControlPanel::arm_away(optional code) { auto call = this->make_call(); call.arm_away(); diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.h b/esphome/components/alarm_control_panel/alarm_control_panel.h index dc0b92df76..85c2b2148e 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel.h @@ -89,6 +89,18 @@ class AlarmControlPanel : public EntityBase { */ void add_on_cleared_callback(std::function &&callback); + /** Add a callback for when a chime zone goes from closed to open + * + * @param callback The callback function + */ + void add_on_chime_callback(std::function &&callback); + + /** Add a callback for when a ready state changes + * + * @param callback The callback function + */ + void add_on_ready_callback(std::function &&callback); + /** A numeric representation of the supported features as per HomeAssistant * */ @@ -178,6 +190,10 @@ class AlarmControlPanel : public EntityBase { CallbackManager disarmed_callback_{}; // clear callback CallbackManager cleared_callback_{}; + // chime callback + CallbackManager chime_callback_{}; + // ready callback + CallbackManager ready_callback_{}; }; } // namespace alarm_control_panel diff --git a/esphome/components/alarm_control_panel/automation.h b/esphome/components/alarm_control_panel/automation.h index 8538020c53..2177fb710f 100644 --- a/esphome/components/alarm_control_panel/automation.h +++ b/esphome/components/alarm_control_panel/automation.h @@ -69,6 +69,20 @@ class ClearedTrigger : public Trigger<> { } }; +class ChimeTrigger : public Trigger<> { + public: + explicit ChimeTrigger(AlarmControlPanel *alarm_control_panel) { + alarm_control_panel->add_on_chime_callback([this]() { this->trigger(); }); + } +}; + +class ReadyTrigger : public Trigger<> { + public: + explicit ReadyTrigger(AlarmControlPanel *alarm_control_panel) { + alarm_control_panel->add_on_ready_callback([this]() { this->trigger(); }); + } +}; + template class ArmAwayAction : public Action { public: explicit ArmAwayAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {} diff --git a/esphome/components/am2315c/__init__.py b/esphome/components/am2315c/__init__.py new file mode 100644 index 0000000000..2398e4fa23 --- /dev/null +++ b/esphome/components/am2315c/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@swoboda1337"] diff --git a/esphome/components/am2315c/am2315c.cpp b/esphome/components/am2315c/am2315c.cpp new file mode 100644 index 0000000000..715251a9df --- /dev/null +++ b/esphome/components/am2315c/am2315c.cpp @@ -0,0 +1,200 @@ +// MIT License +// +// Copyright (c) 2023-2024 Rob Tillaart +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#include "am2315c.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace am2315c { + +static const char *const TAG = "am2315c"; + +uint8_t AM2315C::crc8_(uint8_t *data, uint8_t len) { + uint8_t crc = 0xFF; + while (len--) { + crc ^= *data++; + for (uint8_t i = 0; i < 8; i++) { + if (crc & 0x80) { + crc <<= 1; + crc ^= 0x31; + } else { + crc <<= 1; + } + } + } + return crc; +} + +bool AM2315C::reset_register_(uint8_t reg) { + // code based on demo code sent by www.aosong.com + // no further documentation. + // 0x1B returned 18, 0, 4 + // 0x1C returned 18, 65, 0 + // 0x1E returned 18, 8, 0 + // 18 seems to be status register + // other values unknown. + uint8_t data[3]; + data[0] = reg; + data[1] = 0; + data[2] = 0; + ESP_LOGD(TAG, "Reset register: 0x%02x", reg); + if (this->write(data, 3) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Write failed!"); + this->mark_failed(); + return false; + } + delay(5); + if (this->read(data, 3) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Read failed!"); + this->mark_failed(); + return false; + } + delay(10); + data[0] = 0xB0 | reg; + if (this->write(data, 3) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Write failed!"); + this->mark_failed(); + return false; + } + delay(5); + return true; +} + +bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) { + uint32_t raw; + raw = (data[1] << 12) | (data[2] << 4) | (data[3] >> 4); + humidity = raw * 9.5367431640625e-5; + raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; + temperature = raw * 1.9073486328125e-4 - 50; + return this->crc8_(data, 6) == data[6]; +} + +void AM2315C::setup() { + ESP_LOGCONFIG(TAG, "Setting up AM2315C..."); + + // get status + uint8_t status = 0; + if (this->read(&status, 1) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Read failed!"); + this->mark_failed(); + return; + } + + // reset registers if required, according to the datasheet + // this can be required after power on, although this was + // never required during testing + if ((status & 0x18) != 0x18) { + ESP_LOGD(TAG, "Resetting AM2315C registers"); + if (!this->reset_register_(0x1B)) { + this->mark_failed(); + return; + } + if (!this->reset_register_(0x1C)) { + this->mark_failed(); + return; + } + if (!this->reset_register_(0x1E)) { + this->mark_failed(); + return; + } + } +} + +void AM2315C::update() { + // request measurement + uint8_t data[3]; + data[0] = 0xAC; + data[1] = 0x33; + data[2] = 0x00; + if (this->write(data, 3) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Write failed!"); + this->mark_failed(); + return; + } + + // wait for hw to complete measurement + set_timeout(160, [this]() { + // check status + uint8_t status = 0; + if (this->read(&status, 1) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Read failed!"); + this->mark_failed(); + return; + } + if ((status & 0x80) == 0x80) { + ESP_LOGE(TAG, "HW still busy!"); + this->mark_failed(); + return; + } + + // read + uint8_t data[7]; + if (this->read(data, 7) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Read failed!"); + this->mark_failed(); + return; + } + + // check for all zeros + bool zeros = true; + for (uint8_t i : data) { + zeros = zeros && (i == 0); + } + if (zeros) { + ESP_LOGW(TAG, "Data all zeros!"); + this->status_set_warning(); + return; + } + + // convert + float temperature = 0.0; + float humidity = 0.0; + if (this->convert_(data, humidity, temperature)) { + if (this->temperature_sensor_ != nullptr) { + this->temperature_sensor_->publish_state(temperature); + } + if (this->humidity_sensor_ != nullptr) { + this->humidity_sensor_->publish_state(humidity); + } + this->status_clear_warning(); + } else { + ESP_LOGW(TAG, "CRC failed!"); + this->status_set_warning(); + } + }); +} + +void AM2315C::dump_config() { + ESP_LOGCONFIG(TAG, "AM2315C:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with AM2315C failed!"); + } + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); +} + +float AM2315C::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace am2315c +} // namespace esphome diff --git a/esphome/components/am2315c/am2315c.h b/esphome/components/am2315c/am2315c.h new file mode 100644 index 0000000000..9cec40e4c2 --- /dev/null +++ b/esphome/components/am2315c/am2315c.h @@ -0,0 +1,51 @@ +// MIT License +// +// Copyright (c) 2023-2024 Rob Tillaart +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace am2315c { + +class AM2315C : public PollingComponent, public i2c::I2CDevice { + public: + void dump_config() override; + void update() override; + void setup() override; + float get_setup_priority() const override; + + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } + void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } + + protected: + uint8_t crc8_(uint8_t *data, uint8_t len); + bool convert_(uint8_t *data, float &humidity, float &temperature); + bool reset_register_(uint8_t reg); + + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; +}; + +} // namespace am2315c +} // namespace esphome diff --git a/esphome/components/am2315c/sensor.py b/esphome/components/am2315c/sensor.py new file mode 100644 index 0000000000..f3201b05a2 --- /dev/null +++ b/esphome/components/am2315c/sensor.py @@ -0,0 +1,54 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, +) + +DEPENDENCIES = ["i2c"] + +am2315c_ns = cg.esphome_ns.namespace("am2315c") +AM2315C = am2315c_ns.class_("AM2315C", cg.PollingComponent, i2c.I2CDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(AM2315C), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x38)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity_sensor(sens)) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 04db649aef..6b685b8974 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -44,6 +44,7 @@ service APIConnection { rpc button_command (ButtonCommandRequest) returns (void) {} rpc lock_command (LockCommandRequest) returns (void) {} rpc media_player_command (MediaPlayerCommandRequest) returns (void) {} + rpc date_command (DateCommandRequest) returns (void) {} rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {} @@ -216,7 +217,8 @@ message DeviceInfoResponse { string friendly_name = 13; - uint32 voice_assistant_version = 14; + uint32 legacy_voice_assistant_version = 14; + uint32 voice_assistant_feature_flags = 17; string suggested_area = 16; } @@ -600,6 +602,7 @@ message ListEntitiesTextSensorResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; + string device_class = 8; } message TextSensorStateResponse { option (id) = 27; @@ -1420,12 +1423,18 @@ message BluetoothDeviceClearCacheResponse { } // ==================== PUSH TO TALK ==================== +enum VoiceAssistantSubscribeFlag { + VOICE_ASSISTANT_SUBSCRIBE_NONE = 0; + VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1; +} + message SubscribeVoiceAssistantRequest { option (id) = 89; option (source) = SOURCE_CLIENT; option (ifdef) = "USE_VOICE_ASSISTANT"; bool subscribe = 1; + uint32 flags = 2; } enum VoiceAssistantRequestFlag { @@ -1449,6 +1458,7 @@ message VoiceAssistantRequest { string conversation_id = 2; uint32 flags = 3; VoiceAssistantAudioSettings audio_settings = 4; + string wake_word_phrase = 5; } message VoiceAssistantResponse { @@ -1492,6 +1502,16 @@ message VoiceAssistantEventResponse { repeated VoiceAssistantEventData data = 2; } +message VoiceAssistantAudio { + option (id) = 106; + option (source) = SOURCE_BOTH; + option (ifdef) = "USE_VOICE_ASSISTANT"; + + bytes data = 1; + bool end = 2; +} + + // ==================== ALARM CONTROL PANEL ==================== enum AlarmControlPanelState { ALARM_STATE_DISARMED = 0; @@ -1596,3 +1616,45 @@ message TextCommandRequest { fixed32 key = 1; string state = 2; } + + +// ==================== DATETIME DATE ==================== +message ListEntitiesDateResponse { + option (id) = 100; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_DATETIME_DATE"; + + string object_id = 1; + fixed32 key = 2; + string name = 3; + string unique_id = 4; + + string icon = 5; + bool disabled_by_default = 6; + EntityCategory entity_category = 7; +} +message DateStateResponse { + option (id) = 101; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_DATETIME_DATE"; + option (no_delay) = true; + + fixed32 key = 1; + // If the date does not have a valid state yet. + // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller + bool missing_state = 2; + uint32 year = 3; + uint32 month = 4; + uint32 day = 5; +} +message DateCommandRequest { + option (id) = 102; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_DATETIME_DATE"; + option (no_delay) = true; + + fixed32 key = 1; + uint32 year = 2; + uint32 month = 3; + uint32 day = 4; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index d5ab00a822..e9607f7f77 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -118,7 +118,9 @@ void APIConnection::loop() { this->list_entities_iterator_.advance(); this->initial_state_iterator_.advance(); - const uint32_t keepalive = 60000; + static uint32_t keepalive = 60000; + static uint8_t max_ping_retries = 60; + static uint16_t ping_retry_interval = 1000; const uint32_t now = millis(); if (this->sent_ping_) { // Disconnect if not responded within 2.5*keepalive @@ -126,10 +128,24 @@ void APIConnection::loop() { on_fatal_error(); ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_combined_info_.c_str()); } - } else if (now - this->last_traffic_ > keepalive) { + } else if (now - this->last_traffic_ > keepalive && now > this->next_ping_retry_) { ESP_LOGVV(TAG, "Sending keepalive PING..."); - this->sent_ping_ = true; - this->send_ping_request(PingRequest()); + this->sent_ping_ = this->send_ping_request(PingRequest()); + if (!this->sent_ping_) { + this->next_ping_retry_ = now + ping_retry_interval; + this->ping_retries_++; + if (this->ping_retries_ >= max_ping_retries) { + on_fatal_error(); + ESP_LOGE(TAG, "%s: Sending keepalive failed %d time(s). Disconnecting...", this->client_combined_info_.c_str(), + this->ping_retries_); + } else if (this->ping_retries_ >= 10) { + ESP_LOGW(TAG, "%s: Sending keepalive failed %d time(s), will retry in %d ms", + this->client_combined_info_.c_str(), this->ping_retries_, ping_retry_interval); + } else { + ESP_LOGD(TAG, "%s: Sending keepalive failed %d time(s), will retry in %d ms", + this->client_combined_info_.c_str(), this->ping_retries_, ping_retry_interval); + } + } } #ifdef USE_ESP32_CAMERA @@ -527,6 +543,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) msg.icon = text_sensor->get_icon(); msg.disabled_by_default = text_sensor->is_disabled_by_default(); msg.entity_category = static_cast(text_sensor->get_entity_category()); + msg.device_class = text_sensor->get_device_class(); return this->send_list_entities_text_sensor_response(msg); } #endif @@ -681,6 +698,43 @@ void APIConnection::number_command(const NumberCommandRequest &msg) { } #endif +#ifdef USE_DATETIME_DATE +bool APIConnection::send_date_state(datetime::DateEntity *date) { + if (!this->state_subscription_) + return false; + + DateStateResponse resp{}; + resp.key = date->get_object_id_hash(); + resp.missing_state = !date->has_state(); + resp.year = date->year; + resp.month = date->month; + resp.day = date->day; + return this->send_date_state_response(resp); +} +bool APIConnection::send_date_info(datetime::DateEntity *date) { + ListEntitiesDateResponse msg; + msg.key = date->get_object_id_hash(); + msg.object_id = date->get_object_id(); + if (date->has_own_name()) + msg.name = date->get_name(); + msg.unique_id = get_default_unique_id("date", date); + msg.icon = date->get_icon(); + msg.disabled_by_default = date->is_disabled_by_default(); + msg.entity_category = static_cast(date->get_entity_category()); + + return this->send_list_entities_date_response(msg); +} +void APIConnection::date_command(const DateCommandRequest &msg) { + datetime::DateEntity *date = App.get_date_by_key(msg.key); + if (date == nullptr) + return; + + auto call = date->make_call(); + call.set_date(msg.year, msg.month, msg.day); + call.perform(); +} +#endif + #ifdef USE_TEXT bool APIConnection::send_text_state(text::Text *text, std::string state) { if (!this->state_subscription_) @@ -986,10 +1040,15 @@ void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &ms voice_assistant::global_voice_assistant->failed_to_start(); return; } - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - this->helper_->getpeername((struct sockaddr *) &storage, &len); - voice_assistant::global_voice_assistant->start_streaming(&storage, msg.port); + if (msg.port == 0) { + // Use API Audio + voice_assistant::global_voice_assistant->start_streaming(); + } else { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + this->helper_->getpeername((struct sockaddr *) &storage, &len); + voice_assistant::global_voice_assistant->start_streaming(&storage, msg.port); + } } }; void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) { @@ -1001,6 +1060,15 @@ void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventR voice_assistant::global_voice_assistant->on_event(msg); } } +void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) { + if (voice_assistant::global_voice_assistant != nullptr) { + if (voice_assistant::global_voice_assistant->get_api_connection() != this) { + return; + } + + voice_assistant::global_voice_assistant->on_audio(msg); + } +}; #endif @@ -1088,7 +1156,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 9; + resp.api_version_minor = 10; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; resp.name = App.get_name(); @@ -1149,7 +1217,8 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags(); #endif #ifdef USE_VOICE_ASSISTANT - resp.voice_assistant_version = voice_assistant::global_voice_assistant->get_version(); + resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version(); + resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags(); #endif return resp; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 09b595bb71..c19c209969 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -72,6 +72,11 @@ class APIConnection : public APIServerConnection { bool send_number_info(number::Number *number); void number_command(const NumberCommandRequest &msg) override; #endif +#ifdef USE_DATETIME_DATE + bool send_date_state(datetime::DateEntity *date); + bool send_date_info(datetime::DateEntity *date); + void date_command(const DateCommandRequest &msg) override; +#endif #ifdef USE_TEXT bool send_text_state(text::Text *text, std::string state); bool send_text_info(text::Text *text); @@ -129,6 +134,7 @@ class APIConnection : public APIServerConnection { void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override; void on_voice_assistant_response(const VoiceAssistantResponse &msg) override; void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; + void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override; #endif #ifdef USE_ALARM_CONTROL_PANEL @@ -140,6 +146,7 @@ class APIConnection : public APIServerConnection { void on_disconnect_response(const DisconnectResponse &value) override; void on_ping_response(const PingResponse &value) override { // we initiated ping + this->ping_retries_ = 0; this->sent_ping_ = false; } void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override; @@ -217,6 +224,8 @@ class APIConnection : public APIServerConnection { bool state_subscription_{false}; int log_subscription_{ESPHOME_LOG_LEVEL_NONE}; uint32_t last_traffic_; + uint32_t next_ping_retry_{0}; + uint8_t ping_retries_{0}; bool sent_ping_{false}; bool service_call_subscription_{false}; bool next_close_ = false; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 8dd34e7ef1..f6325d0854 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -410,6 +410,19 @@ const char *proto_enum_to_string(enums::Bluet } #endif #ifdef HAS_PROTO_MESSAGE_DUMP +template<> +const char *proto_enum_to_string(enums::VoiceAssistantSubscribeFlag value) { + switch (value) { + case enums::VOICE_ASSISTANT_SUBSCRIBE_NONE: + return "VOICE_ASSISTANT_SUBSCRIBE_NONE"; + case enums::VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO: + return "VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO"; + default: + return "UNKNOWN"; + } +} +#endif +#ifdef HAS_PROTO_MESSAGE_DUMP template<> const char *proto_enum_to_string(enums::VoiceAssistantRequestFlag value) { switch (value) { case enums::VOICE_ASSISTANT_REQUEST_NONE: @@ -716,7 +729,11 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 14: { - this->voice_assistant_version = value.as_uint32(); + this->legacy_voice_assistant_version = value.as_uint32(); + return true; + } + case 17: { + this->voice_assistant_feature_flags = value.as_uint32(); return true; } default: @@ -784,7 +801,8 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(15, this->bluetooth_proxy_feature_flags); buffer.encode_string(12, this->manufacturer); buffer.encode_string(13, this->friendly_name); - buffer.encode_uint32(14, this->voice_assistant_version); + buffer.encode_uint32(14, this->legacy_voice_assistant_version); + buffer.encode_uint32(17, this->voice_assistant_feature_flags); buffer.encode_string(16, this->suggested_area); } #ifdef HAS_PROTO_MESSAGE_DUMP @@ -850,8 +868,13 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append("'").append(this->friendly_name).append("'"); out.append("\n"); - out.append(" voice_assistant_version: "); - sprintf(buffer, "%" PRIu32, this->voice_assistant_version); + out.append(" legacy_voice_assistant_version: "); + sprintf(buffer, "%" PRIu32, this->legacy_voice_assistant_version); + out.append(buffer); + out.append("\n"); + + out.append(" voice_assistant_feature_flags: "); + sprintf(buffer, "%" PRIu32, this->voice_assistant_feature_flags); out.append(buffer); out.append("\n"); @@ -2721,6 +2744,10 @@ bool ListEntitiesTextSensorResponse::decode_length(uint32_t field_id, ProtoLengt this->icon = value.as_string(); return true; } + case 8: { + this->device_class = value.as_string(); + return true; + } default: return false; } @@ -2743,6 +2770,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); + buffer.encode_string(8, this->device_class); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { @@ -2776,6 +2804,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" device_class: "); + out.append("'").append(this->device_class).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -3848,6 +3880,7 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { sprintf(buffer, "%g", this->visual_max_humidity); out.append(buffer); out.append("\n"); + out.append("}"); } #endif bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -4015,6 +4048,7 @@ void ClimateStateResponse::dump_to(std::string &out) const { sprintf(buffer, "%g", this->target_humidity); out.append(buffer); out.append("\n"); + out.append("}"); } #endif bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -6503,11 +6537,18 @@ bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarIn this->subscribe = value.as_bool(); return true; } + case 2: { + this->flags = value.as_uint32(); + return true; + } default: return false; } } -void SubscribeVoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->subscribe); } +void SubscribeVoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_bool(1, this->subscribe); + buffer.encode_uint32(2, this->flags); +} #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; @@ -6515,6 +6556,11 @@ void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const { out.append(" subscribe: "); out.append(YESNO(this->subscribe)); out.append("\n"); + + out.append(" flags: "); + sprintf(buffer, "%" PRIu32, this->flags); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif @@ -6592,6 +6638,10 @@ bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimite this->audio_settings = value.as_message(); return true; } + case 5: { + this->wake_word_phrase = value.as_string(); + return true; + } default: return false; } @@ -6601,6 +6651,7 @@ void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(2, this->conversation_id); buffer.encode_uint32(3, this->flags); buffer.encode_message(4, this->audio_settings); + buffer.encode_string(5, this->wake_word_phrase); } #ifdef HAS_PROTO_MESSAGE_DUMP void VoiceAssistantRequest::dump_to(std::string &out) const { @@ -6622,6 +6673,10 @@ void VoiceAssistantRequest::dump_to(std::string &out) const { out.append(" audio_settings: "); this->audio_settings.dump_to(out); out.append("\n"); + + out.append(" wake_word_phrase: "); + out.append("'").append(this->wake_word_phrase).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -6732,6 +6787,44 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const { out.append("}"); } #endif +bool VoiceAssistantAudio::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->end = value.as_bool(); + return true; + } + default: + return false; + } +} +bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->data); + buffer.encode_bool(2, this->end); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantAudio::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("VoiceAssistantAudio {\n"); + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + + out.append(" end: "); + out.append(YESNO(this->end)); + out.append("\n"); + out.append("}"); +} +#endif bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 6: { @@ -7164,6 +7257,225 @@ void TextCommandRequest::dump_to(std::string &out) const { out.append("}"); } #endif +bool ListEntitiesDateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 6: { + this->disabled_by_default = value.as_bool(); + return true; + } + case 7: { + this->entity_category = value.as_enum(); + return true; + } + default: + return false; + } +} +bool ListEntitiesDateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->object_id = value.as_string(); + return true; + } + case 3: { + this->name = value.as_string(); + return true; + } + case 4: { + this->unique_id = value.as_string(); + return true; + } + case 5: { + this->icon = value.as_string(); + return true; + } + default: + return false; + } +} +bool ListEntitiesDateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 2: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->object_id); + buffer.encode_fixed32(2, this->key); + buffer.encode_string(3, this->name); + buffer.encode_string(4, this->unique_id); + buffer.encode_string(5, this->icon); + buffer.encode_bool(6, this->disabled_by_default); + buffer.encode_enum(7, this->entity_category); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void ListEntitiesDateResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("ListEntitiesDateResponse {\n"); + out.append(" object_id: "); + out.append("'").append(this->object_id).append("'"); + out.append("\n"); + + out.append(" key: "); + sprintf(buffer, "%" PRIu32, this->key); + out.append(buffer); + out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); + + out.append(" unique_id: "); + out.append("'").append(this->unique_id).append("'"); + out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); + out.append("}"); +} +#endif +bool DateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->missing_state = value.as_bool(); + return true; + } + case 3: { + this->year = value.as_uint32(); + return true; + } + case 4: { + this->month = value.as_uint32(); + return true; + } + case 5: { + this->day = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool DateStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void DateStateResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_bool(2, this->missing_state); + buffer.encode_uint32(3, this->year); + buffer.encode_uint32(4, this->month); + buffer.encode_uint32(5, this->day); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void DateStateResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("DateStateResponse {\n"); + out.append(" key: "); + sprintf(buffer, "%" PRIu32, this->key); + out.append(buffer); + out.append("\n"); + + out.append(" missing_state: "); + out.append(YESNO(this->missing_state)); + out.append("\n"); + + out.append(" year: "); + sprintf(buffer, "%" PRIu32, this->year); + out.append(buffer); + out.append("\n"); + + out.append(" month: "); + sprintf(buffer, "%" PRIu32, this->month); + out.append(buffer); + out.append("\n"); + + out.append(" day: "); + sprintf(buffer, "%" PRIu32, this->day); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool DateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->year = value.as_uint32(); + return true; + } + case 3: { + this->month = value.as_uint32(); + return true; + } + case 4: { + this->day = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool DateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void DateCommandRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_uint32(2, this->year); + buffer.encode_uint32(3, this->month); + buffer.encode_uint32(4, this->day); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void DateCommandRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("DateCommandRequest {\n"); + out.append(" key: "); + sprintf(buffer, "%" PRIu32, this->key); + out.append(buffer); + out.append("\n"); + + out.append(" year: "); + sprintf(buffer, "%" PRIu32, this->year); + out.append(buffer); + out.append("\n"); + + out.append(" month: "); + sprintf(buffer, "%" PRIu32, this->month); + out.append(buffer); + out.append("\n"); + + out.append(" day: "); + sprintf(buffer, "%" PRIu32, this->day); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 02fc7b88f8..e361e6b8e1 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -165,6 +165,10 @@ enum BluetoothDeviceRequestType : uint32_t { BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5, BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6, }; +enum VoiceAssistantSubscribeFlag : uint32_t { + VOICE_ASSISTANT_SUBSCRIBE_NONE = 0, + VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1, +}; enum VoiceAssistantRequestFlag : uint32_t { VOICE_ASSISTANT_REQUEST_NONE = 0, VOICE_ASSISTANT_REQUEST_USE_VAD = 1, @@ -327,7 +331,8 @@ class DeviceInfoResponse : public ProtoMessage { uint32_t bluetooth_proxy_feature_flags{0}; std::string manufacturer{}; std::string friendly_name{}; - uint32_t voice_assistant_version{0}; + uint32_t legacy_voice_assistant_version{0}; + uint32_t voice_assistant_feature_flags{0}; std::string suggested_area{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -713,6 +718,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { std::string icon{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string device_class{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1673,6 +1679,7 @@ class BluetoothDeviceClearCacheResponse : public ProtoMessage { class SubscribeVoiceAssistantRequest : public ProtoMessage { public: bool subscribe{false}; + uint32_t flags{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1701,6 +1708,7 @@ class VoiceAssistantRequest : public ProtoMessage { std::string conversation_id{}; uint32_t flags{0}; VoiceAssistantAudioSettings audio_settings{}; + std::string wake_word_phrase{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1747,6 +1755,19 @@ class VoiceAssistantEventResponse : public ProtoMessage { bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class VoiceAssistantAudio : public ProtoMessage { + public: + std::string data{}; + bool end{false}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { public: std::string object_id{}; @@ -1848,6 +1869,56 @@ class TextCommandRequest : public ProtoMessage { bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; +class ListEntitiesDateResponse : public ProtoMessage { + public: + std::string object_id{}; + uint32_t key{0}; + std::string name{}; + std::string unique_id{}; + std::string icon{}; + bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class DateStateResponse : public ProtoMessage { + public: + uint32_t key{0}; + bool missing_state{false}; + uint32_t year{0}; + uint32_t month{0}; + uint32_t day{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class DateCommandRequest : public ProtoMessage { + public: + uint32_t key{0}; + uint32_t year{0}; + uint32_t month{0}; + uint32_t day{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 4c49c09c8e..2067f530ce 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -476,6 +476,14 @@ bool APIServerConnectionBase::send_voice_assistant_request(const VoiceAssistantR #endif #ifdef USE_VOICE_ASSISTANT #endif +#ifdef USE_VOICE_ASSISTANT +bool APIServerConnectionBase::send_voice_assistant_audio(const VoiceAssistantAudio &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_voice_assistant_audio: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 106); +} +#endif #ifdef USE_ALARM_CONTROL_PANEL bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response( const ListEntitiesAlarmControlPanelResponse &msg) { @@ -513,6 +521,24 @@ bool APIServerConnectionBase::send_text_state_response(const TextStateResponse & #endif #ifdef USE_TEXT #endif +#ifdef USE_DATETIME_DATE +bool APIServerConnectionBase::send_list_entities_date_response(const ListEntitiesDateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_list_entities_date_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 100); +} +#endif +#ifdef USE_DATETIME_DATE +bool APIServerConnectionBase::send_date_state_response(const DateStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_date_state_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 101); +} +#endif +#ifdef USE_DATETIME_DATE +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { @@ -942,6 +968,28 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ESP_LOGVV(TAG, "on_text_command_request: %s", msg.dump().c_str()); #endif this->on_text_command_request(msg); +#endif + break; + } + case 102: { +#ifdef USE_DATETIME_DATE + DateCommandRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_date_command_request: %s", msg.dump().c_str()); +#endif + this->on_date_command_request(msg); +#endif + break; + } + case 106: { +#ifdef USE_VOICE_ASSISTANT + VoiceAssistantAudio msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_voice_assistant_audio: %s", msg.dump().c_str()); +#endif + this->on_voice_assistant_audio(msg); #endif break; } @@ -1218,6 +1266,19 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma 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_BLUETOOTH_PROXY void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( const SubscribeBluetoothLEAdvertisementsRequest &msg) { diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 20639fc139..effcfc30f4 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -240,6 +240,10 @@ class APIServerConnectionBase : public ProtoService { #ifdef USE_VOICE_ASSISTANT virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){}; #endif +#ifdef USE_VOICE_ASSISTANT + bool send_voice_assistant_audio(const VoiceAssistantAudio &msg); + virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){}; +#endif #ifdef USE_ALARM_CONTROL_PANEL bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg); #endif @@ -257,6 +261,15 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_TEXT virtual void on_text_command_request(const TextCommandRequest &value){}; +#endif +#ifdef USE_DATETIME_DATE + bool send_list_entities_date_response(const ListEntitiesDateResponse &msg); +#endif +#ifdef USE_DATETIME_DATE + bool send_date_state_response(const DateStateResponse &msg); +#endif +#ifdef USE_DATETIME_DATE + virtual void on_date_command_request(const DateCommandRequest &value){}; #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -312,6 +325,9 @@ class APIServerConnection : public APIServerConnectionBase { #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_BLUETOOTH_PROXY virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; #endif @@ -398,6 +414,9 @@ class APIServerConnection : public APIServerConnectionBase { #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_BLUETOOTH_PROXY void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; #endif diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 0348112fcd..b17555bb49 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -255,6 +255,15 @@ void APIServer::on_number_update(number::Number *obj, float state) { } #endif +#ifdef USE_DATETIME_DATE +void APIServer::on_date_update(datetime::DateEntity *obj) { + if (obj->is_internal()) + return; + for (auto &c : this->clients_) + c->send_date_state(obj); +} +#endif + #ifdef USE_TEXT void APIServer::on_text_update(text::Text *obj, const std::string &state) { if (obj->is_internal()) @@ -319,7 +328,7 @@ void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeo #ifdef USE_HOMEASSISTANT_TIME void APIServer::request_time() { for (auto &client : this->clients_) { - if (!client->remove_ && client->connection_state_ == APIConnection::ConnectionState::CONNECTED) + if (!client->remove_ && client->is_authenticated()) client->send_time_request(); } } diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 9605a196b3..c12355cc8b 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -66,6 +66,9 @@ class APIServer : public Component, public Controller { #ifdef USE_NUMBER void on_number_update(number::Number *obj, float state) override; #endif +#ifdef USE_DATETIME_DATE + void on_date_update(datetime::DateEntity *obj) override; +#endif #ifdef USE_TEXT void on_text_update(text::Text *obj, const std::string &state) override; #endif diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index fe359143b1..cd1841de5e 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -1,8 +1,8 @@ #include "list_entities.h" -#include "esphome/core/util.h" -#include "esphome/core/log.h" -#include "esphome/core/application.h" #include "api_connection.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" namespace esphome { namespace api { @@ -60,6 +60,10 @@ bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this-> bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); } #endif +#ifdef USE_DATETIME_DATE +bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_info(date); } +#endif + #ifdef USE_TEXT bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); } #endif diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index e19c0d99f0..b49867048d 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -46,6 +46,9 @@ class ListEntitiesIterator : public ComponentIterator { #ifdef USE_NUMBER bool on_number(number::Number *number) override; #endif +#ifdef USE_DATETIME_DATE + bool on_date(datetime::DateEntity *date) override; +#endif #ifdef USE_TEXT bool on_text(text::Text *text) override; #endif diff --git a/esphome/components/api/subscribe_state.cpp b/esphome/components/api/subscribe_state.cpp index aff156cafd..4e7216ddef 100644 --- a/esphome/components/api/subscribe_state.cpp +++ b/esphome/components/api/subscribe_state.cpp @@ -42,6 +42,9 @@ bool InitialStateIterator::on_number(number::Number *number) { return this->client_->send_number_state(number, number->state); } #endif +#ifdef USE_DATETIME_DATE +bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_state(date); } +#endif #ifdef USE_TEXT bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); } #endif diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index 1f27387cf2..4a96659b76 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -43,6 +43,9 @@ class InitialStateIterator : public ComponentIterator { #ifdef USE_NUMBER bool on_number(number::Number *number) override; #endif +#ifdef USE_DATETIME_DATE + bool on_date(datetime::DateEntity *date) override; +#endif #ifdef USE_TEXT bool on_text(text::Text *text) override; #endif diff --git a/esphome/components/as5600/__init__.py b/esphome/components/as5600/__init__.py new file mode 100644 index 0000000000..1840b22768 --- /dev/null +++ b/esphome/components/as5600/__init__.py @@ -0,0 +1,228 @@ +from esphome import pins +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import ( + CONF_ID, + CONF_DIR_PIN, + CONF_DIRECTION, + CONF_HYSTERESIS, + CONF_RANGE, +) + +CODEOWNERS = ["@ammmze"] +DEPENDENCIES = ["i2c"] +MULTI_CONF = True + +as5600_ns = cg.esphome_ns.namespace("as5600") +AS5600Component = as5600_ns.class_("AS5600Component", cg.Component, i2c.I2CDevice) + +DIRECTION = { + "CLOCKWISE": 0, + "COUNTERCLOCKWISE": 1, +} + +POWER_MODE = { + "NOMINAL": 0, + "LOW1": 1, + "LOW2": 2, + "LOW3": 3, +} + +HYSTERESIS = { + "NONE": 0, + "LSB1": 1, + "LSB2": 2, + "LSB3": 3, +} + +SLOW_FILTER = { + "16X": 0, + "8X": 1, + "4X": 2, + "2X": 3, +} + +FAST_FILTER = { + "NONE": 0, + "LSB6": 1, + "LSB7": 2, + "LSB9": 3, + "LSB18": 4, + "LSB21": 5, + "LSB24": 6, + "LSB10": 7, +} + +CONF_ANGLE = "angle" +CONF_RAW_ANGLE = "raw_angle" +CONF_RAW_POSITION = "raw_position" +CONF_WATCHDOG = "watchdog" +CONF_POWER_MODE = "power_mode" +CONF_SLOW_FILTER = "slow_filter" +CONF_FAST_FILTER = "fast_filter" +CONF_START_POSITION = "start_position" +CONF_END_POSITION = "end_position" + + +RESOLUTION = 4096 +MAX_POSITION = RESOLUTION - 1 +ANGLE_TO_POSITION = RESOLUTION / 360 +POSITION_TO_ANGLE = 360 / RESOLUTION +# validate min range of 18deg (per datasheet) ... though i seem to get valid values down to a range of 192steps (16.875deg) +MIN_RANGE = round(18 * ANGLE_TO_POSITION) + + +def angle(min=-360, max=360): + return cv.All( + cv.float_with_unit("angle", "(°|deg)"), cv.float_range(min=min, max=max) + ) + + +def angle_to_position(value, min=-360, max=360): + try: + value = angle(min=min, max=max)(value) + return (RESOLUTION + round(value * ANGLE_TO_POSITION)) % RESOLUTION + except cv.Invalid as e: + raise cv.Invalid(f"When using angle, {e.error_message}") + + +def percent_to_position(value): + value = cv.possibly_negative_percentage(value) + return (RESOLUTION + round(value * RESOLUTION)) % RESOLUTION + + +def position(min=-MAX_POSITION, max=MAX_POSITION): + """Validate that the config option is a position. + Accepts integers, degrees, or percentage (of 360 degrees). + """ + + def validator(value): + if isinstance(value, str) and value.endswith("%"): + value = percent_to_position(value) + + if isinstance(value, str) and (value.endswith("°") or value.endswith("deg")): + return angle_to_position( + value, + min=round(min * POSITION_TO_ANGLE), + max=round(max * POSITION_TO_ANGLE), + ) + + return cv.int_range(min=min, max=max)(value) + + return validator + + +def position_range(): + """Validate that value given is a valid range for the device. + A valid range is one of the following: + - a value of 0 (meaning full range) + - 18 thru 360 degrees + - negative 360 thru negative 18 degrees (notes: these are normalized to their positive values, accepting negatives is for convenience) + """ + zero_validator = position(min=0, max=0) + negative_validator = cv.Any( + position(min=-MAX_POSITION, max=-MIN_RANGE), + zero_validator, + ) + positive_validator = cv.Any( + position(min=MIN_RANGE, max=MAX_POSITION), + zero_validator, + ) + + def validator(value): + is_negative_str = isinstance(value, str) and value.startswith("-") + is_negative_num = isinstance(value, (float, int)) and value < 0 + if is_negative_str or is_negative_num: + return negative_validator(value) + return positive_validator(value) + + return validator + + +def has_valid_range_config(): + """Validate that that the config start + end position results in a valid + positional range, which must be >= 18degrees + """ + range_validator = position_range() + + def validator(config): + # if we don't have an end position, then there is nothing to do + if CONF_END_POSITION not in config: + return config + + # determine the range by taking the difference from the end and start + range = config[CONF_END_POSITION] - config[CONF_START_POSITION] + + # but need to account for start position being greater than end position + # where the range rolls back around the 0 position + if config[CONF_END_POSITION] < config[CONF_START_POSITION]: + range = RESOLUTION + config[CONF_END_POSITION] - config[CONF_START_POSITION] + + try: + range_validator(range) + return config + except cv.Invalid as e: + raise cv.Invalid( + f"The range between start and end position is invalid. It was was {range} but {e.error_message}" + ) + + return validator + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(AS5600Component), + cv.Optional(CONF_DIR_PIN): pins.gpio_input_pin_schema, + cv.Optional(CONF_DIRECTION, default="CLOCKWISE"): cv.enum( + DIRECTION, upper=True + ), + cv.Optional(CONF_WATCHDOG, default=False): cv.boolean, + cv.Optional(CONF_POWER_MODE, default="NOMINAL"): cv.enum( + POWER_MODE, upper=True, space="" + ), + cv.Optional(CONF_HYSTERESIS, default="NONE"): cv.enum( + HYSTERESIS, upper=True, space="" + ), + cv.Optional(CONF_SLOW_FILTER, default="16X"): cv.enum( + SLOW_FILTER, upper=True, space="" + ), + cv.Optional(CONF_FAST_FILTER, default="NONE"): cv.enum( + FAST_FILTER, upper=True, space="" + ), + cv.Optional(CONF_START_POSITION, default=0): position(), + cv.Optional(CONF_END_POSITION): position(), + cv.Optional(CONF_RANGE): position_range(), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x36)), + # ensure end_position and range are mutually exclusive + cv.has_at_most_one_key(CONF_END_POSITION, CONF_RANGE), + has_valid_range_config(), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + cg.add(var.set_direction(config[CONF_DIRECTION])) + cg.add(var.set_watchdog(config[CONF_WATCHDOG])) + cg.add(var.set_power_mode(config[CONF_POWER_MODE])) + cg.add(var.set_hysteresis(config[CONF_HYSTERESIS])) + cg.add(var.set_slow_filter(config[CONF_SLOW_FILTER])) + cg.add(var.set_fast_filter(config[CONF_FAST_FILTER])) + cg.add(var.set_start_position(config[CONF_START_POSITION])) + + if dir_pin_config := config.get(CONF_DIR_PIN): + pin = await cg.gpio_pin_expression(dir_pin_config) + cg.add(var.set_dir_pin(pin)) + + if (end_position_config := config.get(CONF_END_POSITION, None)) is not None: + cg.add(var.set_end_position(end_position_config)) + + if (range_config := config.get(CONF_RANGE, None)) is not None: + cg.add(var.set_range(range_config)) diff --git a/esphome/components/as5600/as5600.cpp b/esphome/components/as5600/as5600.cpp new file mode 100644 index 0000000000..3fe7eab58d --- /dev/null +++ b/esphome/components/as5600/as5600.cpp @@ -0,0 +1,138 @@ +#include "as5600.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace as5600 { + +static const char *const TAG = "as5600"; + +// Configuration registers +static const uint8_t REGISTER_ZMCO = 0x00; // 8 bytes / R +static const uint8_t REGISTER_ZPOS = 0x01; // 16 bytes / RW +static const uint8_t REGISTER_MPOS = 0x03; // 16 bytes / RW +static const uint8_t REGISTER_MANG = 0x05; // 16 bytes / RW +static const uint8_t REGISTER_CONF = 0x07; // 16 bytes / RW + +// Output registers +static const uint8_t REGISTER_ANGLE_RAW = 0x0C; // 16 bytes / R +static const uint8_t REGISTER_ANGLE = 0x0E; // 16 bytes / R + +// Status registers +static const uint8_t REGISTER_STATUS = 0x0B; // 8 bytes / R +static const uint8_t REGISTER_AGC = 0x1A; // 8 bytes / R +static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R + +void AS5600Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up AS5600..."); + + if (!this->read_byte(REGISTER_STATUS).has_value()) { + this->mark_failed(); + return; + } + + // configuration direction pin, if given + // the dir pin on the chip should be low for clockwise + // and high for counterclockwise. If the pin is left floating + // the reported positions will be erratic. + if (this->dir_pin_ != nullptr) { + this->dir_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->dir_pin_->digital_write(this->direction_ == 1); + } + + // build config register + // take the value, shift it left, and add mask to it to ensure we + // are only changing the bits appropriate for that setting in the + // off chance we somehow have bad value in there and it makes for + // a nice visual for the bit positions. + uint16_t config = 0; + // clang-format off + config |= (this->watchdog_ << 13) & 0b0010000000000000; + config |= (this->fast_filter_ << 10) & 0b0001110000000000; + config |= (this->slow_filter_ << 8) & 0b0000001100000000; + config |= (this->pwm_frequency_ << 6) & 0b0000000011000000; + config |= (this->output_mode_ << 4) & 0b0000000000110000; + config |= (this->hysteresis_ << 2) & 0b0000000000001100; + config |= (this->power_mode_ << 0) & 0b0000000000000011; + // clang-format on + + // write config to config register + if (!this->write_byte_16(REGISTER_CONF, config)) { + this->mark_failed(); + return; + } + + // configure the start position + this->write_byte_16(REGISTER_ZPOS, this->start_position_); + + // configure either end position or max angle + if (this->end_mode_ == END_MODE_POSITION) { + this->write_byte_16(REGISTER_MPOS, this->end_position_); + } else { + this->write_byte_16(REGISTER_MANG, this->end_position_); + } + + // calculate the raw max from end position or start + range + this->raw_max_ = this->end_mode_ == END_MODE_POSITION ? this->end_position_ & 4095 + : (this->start_position_ + this->end_position_) & 4095; + + // calculate allowed range of motion by taking the start from the end + // but only if the end is greater than the start. If the start is greater + // than the end position, then that means we take the start all the way to + // reset point (i.e. 0 deg raw) and then we that with the end position + uint16_t range = this->raw_max_ > this->start_position_ ? this->raw_max_ - this->start_position_ + : (4095 - this->start_position_) + this->raw_max_; + + // range scale is ratio of actual allowed range to the full range + this->range_scale_ = range / 4095.0f; +} + +void AS5600Component::dump_config() { + ESP_LOGCONFIG(TAG, "AS5600:"); + LOG_I2C_DEVICE(this); + + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with AS5600 failed!"); + return; + } + + ESP_LOGCONFIG(TAG, " Watchdog: %d", this->watchdog_); + ESP_LOGCONFIG(TAG, " Fast Filter: %d", this->fast_filter_); + ESP_LOGCONFIG(TAG, " Slow Filter: %d", this->slow_filter_); + ESP_LOGCONFIG(TAG, " Hysteresis: %d", this->hysteresis_); + ESP_LOGCONFIG(TAG, " Start Position: %d", this->start_position_); + if (this->end_mode_ == END_MODE_POSITION) { + ESP_LOGCONFIG(TAG, " End Position: %d", this->end_position_); + } else { + ESP_LOGCONFIG(TAG, " Range: %d", this->end_position_); + } +} + +bool AS5600Component::in_range(uint16_t raw_position) { + return this->raw_max_ > this->start_position_ + ? raw_position >= this->start_position_ && raw_position <= this->raw_max_ + : raw_position >= this->start_position_ || raw_position <= this->raw_max_; +} + +AS5600MagnetStatus AS5600Component::read_magnet_status() { + uint8_t status = this->reg(REGISTER_STATUS).get() >> 3 & 0b000111; + return static_cast(status); +} + +optional AS5600Component::read_position() { + uint16_t pos = 0; + if (!this->read_byte_16(REGISTER_ANGLE, &pos)) { + return {}; + } + return pos; +} + +optional AS5600Component::read_raw_position() { + uint16_t pos = 0; + if (!this->read_byte_16(REGISTER_ANGLE_RAW, &pos)) { + return {}; + } + return pos; +} + +} // namespace as5600 +} // namespace esphome diff --git a/esphome/components/as5600/as5600.h b/esphome/components/as5600/as5600.h new file mode 100644 index 0000000000..fbfd18db40 --- /dev/null +++ b/esphome/components/as5600/as5600.h @@ -0,0 +1,105 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/preferences.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace as5600 { + +static const uint16_t POSITION_COUNT = 4096; +static const float RAW_TO_DEGREES = 360.0 / POSITION_COUNT; +static const float DEGREES_TO_RAW = POSITION_COUNT / 360.0; + +enum EndPositionMode : uint8_t { + // In this mode, the end position is calculated by taking the start position + // and adding the range/positions. For example, you could say start at 90deg, + // and have a range of 180deg and effectively the sensor will report values + // from the physical 90deg thru 270deg. + END_MODE_RANGE, + // In this mode, the end position is explicitly set, and changing the start + // position will NOT change the end position. + END_MODE_POSITION, +}; + +enum OutRangeMode : uint8_t { + // In this mode, the AS5600 chip itself actually reports these values, but + // effectively it splits the out-of-range values in half, and when positioned + // over the half closest to the min/start position, it will report 0 and when + // positioned over the half closes to the max/end position, it will report the + // max/end value. + OUT_RANGE_MODE_MIN_MAX, + // In this mode, when the magnet is positioned outside the configured + // range, the sensor will report NAN, which translates to "Unknown" + // in Home Assistant. + OUT_RANGE_MODE_NAN, +}; + +enum AS5600MagnetStatus : uint8_t { + MAGNET_GONE = 2, // 0b010 / magnet not detected + MAGNET_OK = 4, // 0b100 / magnet just right + MAGNET_STRONG = 5, // 0b101 / magnet too strong + MAGNET_WEAK = 6, // 0b110 / magnet too weak +}; + +class AS5600Component : public Component, public i2c::I2CDevice { + public: + /// Set up the internal sensor array. + void setup() override; + void dump_config() override; + /// HARDWARE_LATE setup priority + float get_setup_priority() const override { return setup_priority::DATA; } + + // configuration setters + void set_dir_pin(InternalGPIOPin *pin) { this->dir_pin_ = pin; } + void set_direction(uint8_t direction) { this->direction_ = direction; } + void set_fast_filter(uint8_t fast_filter) { this->fast_filter_ = fast_filter; } + void set_hysteresis(uint8_t hysteresis) { this->hysteresis_ = hysteresis; } + void set_power_mode(uint8_t power_mode) { this->power_mode_ = power_mode; } + void set_slow_filter(uint8_t slow_filter) { this->slow_filter_ = slow_filter; } + void set_watchdog(bool watchdog) { this->watchdog_ = watchdog; } + bool get_watchdog() { return this->watchdog_; } + void set_start_position(uint16_t start_position) { this->start_position_ = start_position % POSITION_COUNT; } + void set_end_position(uint16_t end_position) { + this->end_position_ = end_position % POSITION_COUNT; + this->end_mode_ = END_MODE_POSITION; + } + void set_range(uint16_t range) { + this->end_position_ = range % POSITION_COUNT; + this->end_mode_ = END_MODE_RANGE; + } + + // Gets the scale value for the configured range. + // For example, if configured to start at 0deg and end at 180deg, the + // range is 50% of the native/raw range, so the range scale would be 0.5. + // If configured to use the full 360deg, the range scale would be 1.0. + float get_range_scale() { return this->range_scale_; } + + // Indicates whether the given *raw* position is within the configured range + bool in_range(uint16_t raw_position); + + AS5600MagnetStatus read_magnet_status(); + optional read_position(); + optional read_raw_position(); + + protected: + InternalGPIOPin *dir_pin_{nullptr}; + uint8_t direction_; + uint8_t fast_filter_; + uint8_t hysteresis_; + uint8_t power_mode_; + uint8_t slow_filter_; + uint8_t pwm_frequency_{0}; + uint8_t output_mode_{0}; + bool watchdog_; + uint16_t start_position_; + uint16_t end_position_{0}; + uint16_t raw_max_; + EndPositionMode end_mode_{END_MODE_RANGE}; + float range_scale_{1.0}; +}; + +} // namespace as5600 +} // namespace esphome diff --git a/esphome/components/as5600/sensor/__init__.py b/esphome/components/as5600/sensor/__init__.py new file mode 100644 index 0000000000..589a66950a --- /dev/null +++ b/esphome/components/as5600/sensor/__init__.py @@ -0,0 +1,119 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_ID, + STATE_CLASS_MEASUREMENT, + ICON_MAGNET, + ICON_ROTATE_RIGHT, + CONF_GAIN, + ENTITY_CATEGORY_DIAGNOSTIC, + CONF_MAGNITUDE, + CONF_STATUS, + CONF_POSITION, +) +from .. import as5600_ns, AS5600Component + +CODEOWNERS = ["@ammmze"] +DEPENDENCIES = ["as5600"] + +AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingComponent) + +CONF_ANGLE = "angle" +CONF_RAW_ANGLE = "raw_angle" +CONF_RAW_POSITION = "raw_position" +CONF_WATCHDOG = "watchdog" +CONF_POWER_MODE = "power_mode" +CONF_SLOW_FILTER = "slow_filter" +CONF_FAST_FILTER = "fast_filter" +CONF_PWM_FREQUENCY = "pwm_frequency" +CONF_BURN_COUNT = "burn_count" +CONF_START_POSITION = "start_position" +CONF_END_POSITION = "end_position" +CONF_OUT_OF_RANGE_MODE = "out_of_range_mode" + +OutOfRangeMode = as5600_ns.enum("OutRangeMode") +OUT_OF_RANGE_MODES = { + "MIN_MAX": OutOfRangeMode.OUT_RANGE_MODE_MIN_MAX, + "NAN": OutOfRangeMode.OUT_RANGE_MODE_NAN, +} + + +CONF_AS5600_ID = "as5600_id" +CONFIG_SCHEMA = ( + sensor.sensor_schema( + AS5600Sensor, + accuracy_decimals=0, + icon=ICON_ROTATE_RIGHT, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.GenerateID(CONF_AS5600_ID): cv.use_id(AS5600Component), + cv.Optional(CONF_OUT_OF_RANGE_MODE): cv.enum( + OUT_OF_RANGE_MODES, upper=True, space="_" + ), + cv.Optional(CONF_RAW_POSITION): sensor.sensor_schema( + accuracy_decimals=0, + icon=ICON_ROTATE_RIGHT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_GAIN): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_MAGNITUDE): sensor.sensor_schema( + accuracy_decimals=0, + icon=ICON_MAGNET, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_STATUS): sensor.sensor_schema( + accuracy_decimals=0, + icon=ICON_MAGNET, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } + ) + .extend(cv.polling_component_schema("60s")) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_parented(var, config[CONF_AS5600_ID]) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + + if out_of_range_mode_config := config.get(CONF_OUT_OF_RANGE_MODE): + cg.add(var.set_out_of_range_mode(out_of_range_mode_config)) + + if angle_config := config.get(CONF_ANGLE): + sens = await sensor.new_sensor(angle_config) + cg.add(var.set_angle_sensor(sens)) + + if raw_angle_config := config.get(CONF_RAW_ANGLE): + sens = await sensor.new_sensor(raw_angle_config) + cg.add(var.set_raw_angle_sensor(sens)) + + if position_config := config.get(CONF_POSITION): + sens = await sensor.new_sensor(position_config) + cg.add(var.set_position_sensor(sens)) + + if raw_position_config := config.get(CONF_RAW_POSITION): + sens = await sensor.new_sensor(raw_position_config) + cg.add(var.set_raw_position_sensor(sens)) + + if gain_config := config.get(CONF_GAIN): + sens = await sensor.new_sensor(gain_config) + cg.add(var.set_gain_sensor(sens)) + + if magnitude_config := config.get(CONF_MAGNITUDE): + sens = await sensor.new_sensor(magnitude_config) + cg.add(var.set_magnitude_sensor(sens)) + + if status_config := config.get(CONF_STATUS): + sens = await sensor.new_sensor(status_config) + cg.add(var.set_status_sensor(sens)) diff --git a/esphome/components/as5600/sensor/as5600_sensor.cpp b/esphome/components/as5600/sensor/as5600_sensor.cpp new file mode 100644 index 0000000000..feb8f6cebf --- /dev/null +++ b/esphome/components/as5600/sensor/as5600_sensor.cpp @@ -0,0 +1,98 @@ +#include "as5600_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace as5600 { + +static const char *const TAG = "as5600.sensor"; + +// Configuration registers +static const uint8_t REGISTER_ZMCO = 0x00; // 8 bytes / R +static const uint8_t REGISTER_ZPOS = 0x01; // 16 bytes / RW +static const uint8_t REGISTER_MPOS = 0x03; // 16 bytes / RW +static const uint8_t REGISTER_MANG = 0x05; // 16 bytes / RW +static const uint8_t REGISTER_CONF = 0x07; // 16 bytes / RW + +// Output registers +static const uint8_t REGISTER_ANGLE_RAW = 0x0C; // 16 bytes / R +static const uint8_t REGISTER_ANGLE = 0x0E; // 16 bytes / R + +// Status registers +static const uint8_t REGISTER_STATUS = 0x0B; // 8 bytes / R +static const uint8_t REGISTER_AGC = 0x1A; // 8 bytes / R +static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R + +float AS5600Sensor::get_setup_priority() const { return setup_priority::DATA; } + +void AS5600Sensor::dump_config() { + LOG_SENSOR("", "AS5600 Sensor", this); + ESP_LOGCONFIG(TAG, " Out of Range Mode: %u", this->out_of_range_mode_); + if (this->angle_sensor_ != nullptr) { + LOG_SENSOR(" ", "Angle Sensor", this->angle_sensor_); + } + if (this->raw_angle_sensor_ != nullptr) { + LOG_SENSOR(" ", "Raw Angle Sensor", this->raw_angle_sensor_); + } + if (this->position_sensor_ != nullptr) { + LOG_SENSOR(" ", "Position Sensor", this->position_sensor_); + } + if (this->raw_position_sensor_ != nullptr) { + LOG_SENSOR(" ", "Raw Position Sensor", this->raw_position_sensor_); + } + if (this->gain_sensor_ != nullptr) { + LOG_SENSOR(" ", "Gain Sensor", this->gain_sensor_); + } + if (this->magnitude_sensor_ != nullptr) { + LOG_SENSOR(" ", "Magnitude Sensor", this->magnitude_sensor_); + } + if (this->status_sensor_ != nullptr) { + LOG_SENSOR(" ", "Status Sensor", this->status_sensor_); + } + LOG_UPDATE_INTERVAL(this); +} + +void AS5600Sensor::update() { + if (this->gain_sensor_ != nullptr) { + this->gain_sensor_->publish_state(this->parent_->reg(REGISTER_AGC).get()); + } + + if (this->magnitude_sensor_ != nullptr) { + uint16_t value = 0; + this->parent_->read_byte_16(REGISTER_MAGNITUDE, &value); + this->magnitude_sensor_->publish_state(value); + } + + // 2 = magnet not detected + // 4 = magnet just right + // 5 = magnet too strong + // 6 = magnet too weak + if (this->status_sensor_ != nullptr) { + this->status_sensor_->publish_state(this->parent_->read_magnet_status()); + } + + auto pos = this->parent_->read_position(); + if (!pos.has_value()) { + this->status_set_warning(); + return; + } + + auto raw = this->parent_->read_raw_position(); + if (!raw.has_value()) { + this->status_set_warning(); + return; + } + + if (this->out_of_range_mode_ == OUT_RANGE_MODE_NAN) { + this->publish_state(this->parent_->in_range(raw.value()) ? pos.value() : NAN); + } else { + this->publish_state(pos.value()); + } + + if (this->raw_position_sensor_ != nullptr) { + this->raw_position_sensor_->publish_state(raw.value()); + } + this->status_clear_warning(); +} + +} // namespace as5600 +} // namespace esphome diff --git a/esphome/components/as5600/sensor/as5600_sensor.h b/esphome/components/as5600/sensor/as5600_sensor.h new file mode 100644 index 0000000000..0af9b01ae5 --- /dev/null +++ b/esphome/components/as5600/sensor/as5600_sensor.h @@ -0,0 +1,43 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/preferences.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/as5600/as5600.h" + +namespace esphome { +namespace as5600 { + +class AS5600Sensor : public PollingComponent, public Parented, public sensor::Sensor { + public: + void update() override; + void dump_config() override; + float get_setup_priority() const override; + + void set_angle_sensor(sensor::Sensor *angle_sensor) { this->angle_sensor_ = angle_sensor; } + void set_raw_angle_sensor(sensor::Sensor *raw_angle_sensor) { this->raw_angle_sensor_ = raw_angle_sensor; } + void set_position_sensor(sensor::Sensor *position_sensor) { this->position_sensor_ = position_sensor; } + void set_raw_position_sensor(sensor::Sensor *raw_position_sensor) { + this->raw_position_sensor_ = raw_position_sensor; + } + void set_gain_sensor(sensor::Sensor *gain_sensor) { this->gain_sensor_ = gain_sensor; } + void set_magnitude_sensor(sensor::Sensor *magnitude_sensor) { this->magnitude_sensor_ = magnitude_sensor; } + void set_status_sensor(sensor::Sensor *status_sensor) { this->status_sensor_ = status_sensor; } + void set_out_of_range_mode(OutRangeMode oor_mode) { this->out_of_range_mode_ = oor_mode; } + OutRangeMode get_out_of_range_mode() { return this->out_of_range_mode_; } + + protected: + sensor::Sensor *angle_sensor_{nullptr}; + sensor::Sensor *raw_angle_sensor_{nullptr}; + sensor::Sensor *position_sensor_{nullptr}; + sensor::Sensor *raw_position_sensor_{nullptr}; + sensor::Sensor *gain_sensor_{nullptr}; + sensor::Sensor *magnitude_sensor_{nullptr}; + sensor::Sensor *status_sensor_{nullptr}; + OutRangeMode out_of_range_mode_{OUT_RANGE_MODE_MIN_MAX}; +}; + +} // namespace as5600 +} // namespace esphome diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 0a69943a25..eae8c0e2df 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -22,7 +22,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): if CORE.is_esp32 or CORE.is_libretiny: # https://github.com/esphome/AsyncTCP/blob/master/library.json - cg.add_library("esphome/AsyncTCP-esphome", "2.0.1") + cg.add_library("esphome/AsyncTCP-esphome", "2.1.3") elif CORE.is_esp8266: # https://github.com/esphome/ESPAsyncTCP cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") diff --git a/esphome/components/at581x/__init__.py b/esphome/components/at581x/__init__.py new file mode 100644 index 0000000000..2860d21f6c --- /dev/null +++ b/esphome/components/at581x/__init__.py @@ -0,0 +1,224 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation, core +from esphome.components import i2c +from esphome.automation import maybe_simple_id +from esphome.const import ( + CONF_ID, + CONF_FREQUENCY, +) + + +CODEOWNERS = ["@X-Ryl669"] +DEPENDENCIES = ["i2c"] +MULTI_CONF = True + + +at581x_ns = cg.esphome_ns.namespace("at581x") +AT581XComponent = at581x_ns.class_("AT581XComponent", cg.Component, i2c.I2CDevice) + + +CONF_AT581X_ID = "at581x_id" + + +CONF_SENSING_DISTANCE = "sensing_distance" +CONF_SENSITIVITY = "sensitivity" +CONF_POWERON_SELFCHECK_TIME = "poweron_selfcheck_time" +CONF_PROTECT_TIME = "protect_time" +CONF_TRIGGER_BASE = "trigger_base" +CONF_TRIGGER_KEEP = "trigger_keep" +CONF_STAGE_GAIN = "stage_gain" +CONF_POWER_CONSUMPTION = "power_consumption" +CONF_HW_FRONTEND_RESET = "hw_frontend_reset" + +RADAR_ALLOWED_FREQ = [ + 5696e6, + 5715e6, + 5730e6, + 5748e6, + 5765e6, + 5784e6, + 5800e6, + 5819e6, + 5836e6, + 5851e6, + 5869e6, + 5888e6, +] +RADAR_ALLOWED_CUR_CONSUMPTION = [ + 48e-6, + 56e-6, + 63e-6, + 70e-6, + 77e-6, + 91e-6, + 105e-6, + 115e-6, + 40e-6, + 44e-6, + 47e-6, + 51e-6, + 54e-6, + 61e-6, + 68e-6, + 78e-6, +] + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(AT581XComponent), + } +) + +CONFIG_SCHEMA = cv.All( + CONFIG_SCHEMA.extend(i2c.i2c_device_schema(0x28)).extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + +# Actions +AT581XResetAction = at581x_ns.class_("AT581XResetAction", automation.Action) +AT581XSettingsAction = at581x_ns.class_("AT581XSettingsAction", automation.Action) + + +@automation.register_action( + "at581x.reset", + AT581XResetAction, + maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(AT581XComponent), + } + ), +) +async def at581x_reset_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + + return var + + +RADAR_SETTINGS_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(AT581XComponent), + cv.Optional(CONF_HW_FRONTEND_RESET): cv.templatable(cv.boolean), + cv.Optional(CONF_FREQUENCY, default="5800MHz"): cv.templatable( + cv.All(cv.frequency, cv.one_of(*RADAR_ALLOWED_FREQ)) + ), + cv.Optional(CONF_SENSING_DISTANCE, default=823): cv.templatable( + cv.int_range(min=0, max=1023) + ), + cv.Optional(CONF_POWERON_SELFCHECK_TIME, default="2000ms"): cv.templatable( + cv.All( + cv.positive_time_period_milliseconds, + cv.Range(max=core.TimePeriod(milliseconds=65535)), + ) + ), + cv.Optional(CONF_POWER_CONSUMPTION, default="70uA"): cv.templatable( + cv.All(cv.current, cv.one_of(*RADAR_ALLOWED_CUR_CONSUMPTION)) + ), + cv.Optional(CONF_PROTECT_TIME, default="1000ms"): cv.templatable( + cv.All( + cv.positive_time_period_milliseconds, + cv.Range( + min=core.TimePeriod(milliseconds=1), + max=core.TimePeriod(milliseconds=65535), + ), + ) + ), + cv.Optional(CONF_TRIGGER_BASE, default="500ms"): cv.templatable( + cv.All( + cv.positive_time_period_milliseconds, + cv.Range( + min=core.TimePeriod(milliseconds=1), + max=core.TimePeriod(milliseconds=65535), + ), + ) + ), + cv.Optional(CONF_TRIGGER_KEEP, default="1500ms"): cv.templatable( + cv.All( + cv.positive_time_period_milliseconds, + cv.Range( + min=core.TimePeriod(milliseconds=1), + max=core.TimePeriod(milliseconds=65535), + ), + ) + ), + cv.Optional(CONF_STAGE_GAIN, default=3): cv.templatable( + cv.int_range(min=0, max=12) + ), + } +).add_extra( + cv.has_at_least_one_key( + CONF_HW_FRONTEND_RESET, + CONF_FREQUENCY, + CONF_SENSING_DISTANCE, + ) +) + + +@automation.register_action( + "at581x.settings", + AT581XSettingsAction, + RADAR_SETTINGS_SCHEMA, +) +async def at581x_settings_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + + # Radar configuration + if frontend_reset := config.get(CONF_HW_FRONTEND_RESET): + template_ = await cg.templatable(frontend_reset, args, int) + cg.add(var.set_hw_frontend_reset(template_)) + + if freq := config.get(CONF_FREQUENCY): + template_ = await cg.templatable(freq, args, float) + template_ = int(template_ / 1000000) + cg.add(var.set_frequency(template_)) + + if sens_dist := config.get(CONF_SENSING_DISTANCE): + template_ = await cg.templatable(sens_dist, args, int) + cg.add(var.set_sensing_distance(template_)) + + if selfcheck := config.get(CONF_POWERON_SELFCHECK_TIME): + template_ = await cg.templatable(selfcheck, args, float) + if isinstance(template_, cv.TimePeriod): + template_ = template_.total_milliseconds + template_ = int(template_) + cg.add(var.set_poweron_selfcheck_time(template_)) + + if protect := config.get(CONF_PROTECT_TIME): + template_ = await cg.templatable(protect, args, float) + if isinstance(template_, cv.TimePeriod): + template_ = template_.total_milliseconds + template_ = int(template_) + cg.add(var.set_protect_time(template_)) + + if trig_base := config.get(CONF_TRIGGER_BASE): + template_ = await cg.templatable(trig_base, args, float) + if isinstance(template_, cv.TimePeriod): + template_ = template_.total_milliseconds + template_ = int(template_) + cg.add(var.set_trigger_base(template_)) + + if trig_keep := config.get(CONF_TRIGGER_KEEP): + template_ = await cg.templatable(trig_keep, args, float) + if isinstance(template_, cv.TimePeriod): + template_ = template_.total_milliseconds + template_ = int(template_) + cg.add(var.set_trigger_keep(template_)) + + if stage_gain := config.get(CONF_STAGE_GAIN): + template_ = await cg.templatable(stage_gain, args, int) + cg.add(var.set_stage_gain(template_)) + + if power := config.get(CONF_POWER_CONSUMPTION): + template_ = await cg.templatable(power, args, float) + template_ = int(template_ * 1000000) + cg.add(var.set_power_consumption(template_)) + + return var diff --git a/esphome/components/at581x/at581x.cpp b/esphome/components/at581x/at581x.cpp new file mode 100644 index 0000000000..eef457f985 --- /dev/null +++ b/esphome/components/at581x/at581x.cpp @@ -0,0 +1,195 @@ +#include "at581x.h" +#include "esphome/core/log.h" + +/* Select gain for AT581X (3dB per step for level1, 6dB per step for level 2), high value = small gain. (p12) */ +const uint8_t GAIN_ADDR_TABLE[] = {0x5c, 0x63}; +const uint8_t GAIN5C_TABLE[] = {0x08, 0x18, 0x28, 0x38, 0x48, 0x58, 0x68, 0x78, 0x88, 0x98, 0xa8, 0xb8, 0xc8}; +const uint8_t GAIN63_TABLE[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; +const uint8_t GAIN61_VALUE = 0xCA; // 0xC0 | 0x02 (freq present) | 0x08 (gain present) + +/*!< Power consumption configuration table (p12). */ +const uint8_t POWER_TABLE[] = {48, 56, 63, 70, 77, 91, 105, 115, 40, 44, 47, 51, 54, 61, 68, 78}; +const uint8_t POWER67_TABLE[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}; +const uint8_t POWER68_TABLE[] = {0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, + 24, 24, 24, 24, 24, 24, 24, 24}; // See Page 12, shift by 3 bits + +/*!< Frequency Configuration table (p14/15 of datasheet). */ +const uint8_t FREQ_ADDR = 0x61; +const uint16_t FREQ_TABLE[] = {5696, 5715, 5730, 5748, 5765, 5784, 5800, 5819, 5836, 5851, 5869, 5888}; +const uint8_t FREQ5F_TABLE[] = {0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x40, 0x41, 0x42, 0x43}; +const uint8_t FREQ60_TABLE[] = {0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e, 0x9e}; + +/*!< Value for RF and analog modules switch (p10). */ +const uint8_t RF_OFF_TABLE[] = {0x46, 0xaa, 0x50}; +const uint8_t RF_ON_TABLE[] = {0x45, 0x55, 0xA0}; +const uint8_t RF_REG_ADDR[] = {0x5d, 0x62, 0x51}; + +/*!< Registers of Lighting delay time. Unit: ms, min 2s (p8) */ +const uint8_t HIGH_LEVEL_DELAY_CONTROL_ADDR = 0x41; /*!< Time_flag_out_ctrl 0x01 */ +const uint8_t HIGH_LEVEL_DELAY_VALUE_ADDR = 0x42; /*!< Time_flag_out_1 Bit<7:0> */ + +const uint8_t RESET_ADDR = 0x00; + +/*!< Sensing distance address */ +const uint8_t SIGNAL_DETECTION_THRESHOLD_ADDR_LO = 0x10; +const uint8_t SIGNAL_DETECTION_THRESHOLD_ADDR_HI = 0x11; + +/*!< Bit field value for power registers */ +const uint8_t POWER_THRESHOLD_ADDR_HI = 0x68; +const uint8_t POWER_THRESHOLD_ADDR_LO = 0x67; +const uint8_t PWR_WORK_TIME_EN = 8; // Reg 0x67 +const uint8_t PWR_BURST_TIME_EN = 32; // Reg 0x68 +const uint8_t PWR_THRESH_EN = 64; // Reg 0x68 +const uint8_t PWR_THRESH_VAL_EN = 128; // Reg 0x67 + +/*!< Times */ +const uint8_t TRIGGER_BASE_TIME_ADDR = 0x3D; // 4 bytes, so up to 0x40 +const uint8_t PROTECT_TIME_ADDR = 0x4E; // 2 bytes, up to 0x4F +const uint8_t TRIGGER_KEEP_TIME_ADDR = 0x42; // 4 bytes, so up to 0x45 +const uint8_t TIME41_VALUE = 1; +const uint8_t SELF_CHECK_TIME_ADDR = 0x38; // 2 bytes, up to 0x39 + +namespace esphome { +namespace at581x { + +static const char *const TAG = "at581x"; + +bool AT581XComponent::i2c_write_reg(uint8_t addr, uint8_t data) { + return this->write_register(addr, &data, 1) == esphome::i2c::NO_ERROR; +} +bool AT581XComponent::i2c_write_reg(uint8_t addr, uint32_t data) { + return this->i2c_write_reg(addr + 0, uint8_t(data & 0xFF)) && + this->i2c_write_reg(addr + 1, uint8_t((data >> 8) & 0xFF)) && + this->i2c_write_reg(addr + 2, uint8_t((data >> 16) & 0xFF)) && + this->i2c_write_reg(addr + 3, uint8_t((data >> 24) & 0xFF)); +} +bool AT581XComponent::i2c_write_reg(uint8_t addr, uint16_t data) { + return this->i2c_write_reg(addr, uint8_t(data & 0xFF)) && this->i2c_write_reg(addr + 1, uint8_t((data >> 8) & 0xFF)); +} + +bool AT581XComponent::i2c_read_reg(uint8_t addr, uint8_t &data) { + return this->read_register(addr, &data, 1) == esphome::i2c::NO_ERROR; +} + +void AT581XComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up AT581X..."); } +void AT581XComponent::dump_config() { LOG_I2C_DEVICE(this); } +#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0])) +bool AT581XComponent::i2c_write_config() { + ESP_LOGCONFIG(TAG, "Writing new config for AT581X..."); + ESP_LOGCONFIG(TAG, "Frequency: %dMHz", this->freq_); + ESP_LOGCONFIG(TAG, "Sensing distance: %d", this->delta_); + ESP_LOGCONFIG(TAG, "Power: %dµA", this->power_); + ESP_LOGCONFIG(TAG, "Gain: %d", this->gain_); + ESP_LOGCONFIG(TAG, "Trigger base time: %dms", this->trigger_base_time_ms_); + ESP_LOGCONFIG(TAG, "Trigger keep time: %dms", this->trigger_keep_time_ms_); + ESP_LOGCONFIG(TAG, "Protect time: %dms", this->protect_time_ms_); + ESP_LOGCONFIG(TAG, "Self check time: %dms", this->self_check_time_ms_); + + // Set frequency point + if (!this->i2c_write_reg(FREQ_ADDR, GAIN61_VALUE)) { + ESP_LOGE(TAG, "Failed to write AT581X Freq mode"); + return false; + } + // Find the current frequency from the table to know what value to write + for (size_t i = 0; i < ARRAY_SIZE(FREQ_TABLE) + 1; i++) { + if (i == ARRAY_SIZE(FREQ_TABLE)) { + ESP_LOGE(TAG, "Set frequency not found"); + return false; + } + if (FREQ_TABLE[i] == this->freq_) { + if (!this->i2c_write_reg(0x5F, FREQ5F_TABLE[i]) || !this->i2c_write_reg(0x60, FREQ60_TABLE[i])) { + ESP_LOGE(TAG, "Failed to write AT581X Freq value"); + return false; + } + break; + } + } + + // Set distance + if (!this->i2c_write_reg(SIGNAL_DETECTION_THRESHOLD_ADDR_LO, (uint8_t) (this->delta_ & 0xFF)) || + !this->i2c_write_reg(SIGNAL_DETECTION_THRESHOLD_ADDR_HI, (uint8_t) (this->delta_ >> 8))) { + ESP_LOGE(TAG, "Failed to write AT581X sensing distance low"); + return false; + } + + // Set power setting + uint8_t pwr67 = PWR_THRESH_VAL_EN | PWR_WORK_TIME_EN, pwr68 = PWR_BURST_TIME_EN | PWR_THRESH_EN; + for (size_t i = 0; i < ARRAY_SIZE(POWER_TABLE) + 1; i++) { + if (i == ARRAY_SIZE(POWER_TABLE)) { + ESP_LOGE(TAG, "Set power not found"); + return false; + } + if (POWER_TABLE[i] == this->power_) { + pwr67 |= POWER67_TABLE[i]; + pwr68 |= POWER68_TABLE[i]; // See Page 12 + break; + } + } + + if (!this->i2c_write_reg(POWER_THRESHOLD_ADDR_LO, pwr67) || !this->i2c_write_reg(POWER_THRESHOLD_ADDR_HI, pwr68)) { + ESP_LOGE(TAG, "Failed to write AT581X power registers"); + return false; + } + + // Set gain + if (!this->i2c_write_reg(GAIN_ADDR_TABLE[0], GAIN5C_TABLE[this->gain_]) || + !this->i2c_write_reg(GAIN_ADDR_TABLE[1], GAIN63_TABLE[this->gain_ >> 1])) { + ESP_LOGE(TAG, "Failed to write AT581X gain registers"); + return false; + } + + // Set times + if (!this->i2c_write_reg(TRIGGER_BASE_TIME_ADDR, (uint32_t) this->trigger_base_time_ms_)) { + ESP_LOGE(TAG, "Failed to write AT581X trigger base time registers"); + return false; + } + if (!this->i2c_write_reg(TRIGGER_KEEP_TIME_ADDR, (uint32_t) this->trigger_keep_time_ms_)) { + ESP_LOGE(TAG, "Failed to write AT581X trigger keep time registers"); + return false; + } + + if (!this->i2c_write_reg(PROTECT_TIME_ADDR, (uint16_t) this->protect_time_ms_)) { + ESP_LOGE(TAG, "Failed to write AT581X protect time registers"); + return false; + } + if (!this->i2c_write_reg(SELF_CHECK_TIME_ADDR, (uint16_t) this->self_check_time_ms_)) { + ESP_LOGE(TAG, "Failed to write AT581X self check time registers"); + return false; + } + + if (!this->i2c_write_reg(0x41, TIME41_VALUE)) { + ESP_LOGE(TAG, "Failed to enable AT581X time registers"); + return false; + } + + // Don't know why it's required in other code, it's not in datasheet + if (!this->i2c_write_reg(0x55, (uint8_t) 0x04)) { + ESP_LOGE(TAG, "Failed to enable AT581X"); + return false; + } + + // Ok, config is written, let's reset the chip so it's using the new config + return this->reset_hardware_frontend(); +} + +// float AT581XComponent::get_setup_priority() const { return 0; } +bool AT581XComponent::reset_hardware_frontend() { + if (!this->i2c_write_reg(RESET_ADDR, (uint8_t) 0) || !this->i2c_write_reg(RESET_ADDR, (uint8_t) 1)) { + ESP_LOGE(TAG, "Failed to reset AT581X hardware frontend"); + return false; + } + return true; +} + +void AT581XComponent::set_rf_mode(bool enable) { + const uint8_t *p = enable ? &RF_ON_TABLE[0] : &RF_OFF_TABLE[0]; + for (size_t i = 0; i < ARRAY_SIZE(RF_REG_ADDR); i++) { + if (!this->i2c_write_reg(RF_REG_ADDR[i], p[i])) { + ESP_LOGE(TAG, "Failed to write AT581X RF mode"); + return; + } + } +} + +} // namespace at581x +} // namespace esphome diff --git a/esphome/components/at581x/at581x.h b/esphome/components/at581x/at581x.h new file mode 100644 index 0000000000..6c637d08c5 --- /dev/null +++ b/esphome/components/at581x/at581x.h @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/defines.h" +#ifdef USE_SWITCH +#include "esphome/components/switch/switch.h" +#endif +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace at581x { + +class AT581XComponent : public Component, public i2c::I2CDevice { +#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(); + } +#endif + + void setup() override; + void dump_config() override; + // float get_setup_priority() const override; + + void set_sensing_distance(int distance) { this->delta_ = 1023 - distance; } + + void set_rf_mode(bool enabled); + void set_frequency(int frequency) { this->freq_ = frequency; } + void set_poweron_selfcheck_time(int value) { this->self_check_time_ms_ = value; } + void set_protect_time(int value) { this->protect_time_ms_ = value; } + void set_trigger_base(int value) { this->trigger_base_time_ms_ = value; } + void set_trigger_keep(int value) { this->trigger_keep_time_ms_ = value; } + void set_stage_gain(int value) { this->gain_ = value; } + void set_power_consumption(int value) { this->power_ = value; } + + bool i2c_write_config(); + bool reset_hardware_frontend(); + bool i2c_write_reg(uint8_t addr, uint8_t data); + bool i2c_write_reg(uint8_t addr, uint32_t data); + bool i2c_write_reg(uint8_t addr, uint16_t data); + bool i2c_read_reg(uint8_t addr, uint8_t &data); + + protected: + 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 */ + int trigger_base_time_ms_; /*!< Default: 500 ms */ + int trigger_keep_time_ms_; /*!< Total trig time = TRIGGER_BASE_TIME + DEF_TRIGGER_KEEP_TIME, minimum: 1 */ + int delta_; /*!< Delta value: 0 ~ 1023, the larger the value, the shorter the distance */ + int gain_; /*!< Default: 9dB */ + int power_; /*!< In µA */ +}; + +} // namespace at581x +} // namespace esphome diff --git a/esphome/components/at581x/automation.h b/esphome/components/at581x/automation.h new file mode 100644 index 0000000000..4863a87565 --- /dev/null +++ b/esphome/components/at581x/automation.h @@ -0,0 +1,71 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/core/helpers.h" + +#include "at581x.h" + +namespace esphome { +namespace at581x { + +template class AT581XResetAction : public Action, public Parented { + public: + void play(Ts... x) { this->parent_->reset_hardware_frontend(); } +}; + +template class AT581XSettingsAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(int8_t, hw_frontend_reset) + TEMPLATABLE_VALUE(int, frequency) + TEMPLATABLE_VALUE(int, sensing_distance) + TEMPLATABLE_VALUE(int, poweron_selfcheck_time) + TEMPLATABLE_VALUE(int, power_consumption) + TEMPLATABLE_VALUE(int, protect_time) + TEMPLATABLE_VALUE(int, trigger_base) + TEMPLATABLE_VALUE(int, trigger_keep) + TEMPLATABLE_VALUE(int, stage_gain) + + void play(Ts... x) { + if (this->frequency_.has_value()) { + int v = this->frequency_.value(x...); + this->parent_->set_frequency(v); + } + if (this->sensing_distance_.has_value()) { + int v = this->sensing_distance_.value(x...); + this->parent_->set_sensing_distance(v); + } + if (this->poweron_selfcheck_time_.has_value()) { + int v = this->poweron_selfcheck_time_.value(x...); + this->parent_->set_poweron_selfcheck_time(v); + } + if (this->power_consumption_.has_value()) { + int v = this->power_consumption_.value(x...); + this->parent_->set_power_consumption(v); + } + if (this->protect_time_.has_value()) { + int v = this->protect_time_.value(x...); + this->parent_->set_protect_time(v); + } + if (this->trigger_base_.has_value()) { + int v = this->trigger_base_.value(x...); + this->parent_->set_trigger_base(v); + } + if (this->trigger_keep_.has_value()) { + int v = this->trigger_keep_.value(x...); + this->parent_->set_trigger_keep(v); + } + if (this->stage_gain_.has_value()) { + int v = this->stage_gain_.value(x...); + this->parent_->set_stage_gain(v); + } + + // This actually perform all the modification on the system + this->parent_->i2c_write_config(); + + if (this->hw_frontend_reset_.has_value() && this->hw_frontend_reset_.value(x...) == true) { + this->parent_->reset_hardware_frontend(); + } + } +}; +} // namespace at581x +} // namespace esphome diff --git a/esphome/components/at581x/switch/__init__.py b/esphome/components/at581x/switch/__init__.py new file mode 100644 index 0000000000..c441b381a3 --- /dev/null +++ b/esphome/components/at581x/switch/__init__.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_SWITCH, + ICON_WIFI, +) +from .. import CONF_AT581X_ID, AT581XComponent, at581x_ns + +DEPENDENCIES = ["at581x"] + +RFSwitch = at581x_ns.class_("RFSwitch", switch.Switch) + +CONFIG_SCHEMA = switch.switch_schema( + RFSwitch, + device_class=DEVICE_CLASS_SWITCH, + icon=ICON_WIFI, +).extend( + cv.Schema( + { + cv.GenerateID(CONF_AT581X_ID): cv.use_id(AT581XComponent), + } + ) +) + + +async def to_code(config): + at581x_component = await cg.get_variable(config[CONF_AT581X_ID]) + s = await switch.new_switch(config) + await cg.register_parented(s, config[CONF_AT581X_ID]) + cg.add(at581x_component.set_rf_power_switch(s)) diff --git a/esphome/components/at581x/switch/rf_switch.cpp b/esphome/components/at581x/switch/rf_switch.cpp new file mode 100644 index 0000000000..f1d03dc8a5 --- /dev/null +++ b/esphome/components/at581x/switch/rf_switch.cpp @@ -0,0 +1,12 @@ +#include "rf_switch.h" + +namespace esphome { +namespace at581x { + +void RFSwitch::write_state(bool state) { + this->publish_state(state); + this->parent_->set_rf_mode(state); +} + +} // namespace at581x +} // namespace esphome diff --git a/esphome/components/at581x/switch/rf_switch.h b/esphome/components/at581x/switch/rf_switch.h new file mode 100644 index 0000000000..920ddbb66a --- /dev/null +++ b/esphome/components/at581x/switch/rf_switch.h @@ -0,0 +1,15 @@ +#pragma once + +#include "esphome/components/switch/switch.h" +#include "../at581x.h" + +namespace esphome { +namespace at581x { + +class RFSwitch : public switch_::Switch, public Parented { + protected: + void write_state(bool state) override; +}; + +} // namespace at581x +} // namespace esphome diff --git a/esphome/components/atm90e26/atm90e26.cpp b/esphome/components/atm90e26/atm90e26.cpp index 42a52c4ccf..6743f1a442 100644 --- a/esphome/components/atm90e26/atm90e26.cpp +++ b/esphome/components/atm90e26/atm90e26.cpp @@ -117,7 +117,7 @@ void ATM90E26Component::setup() { this->write16_(ATM90E26_REGISTER_ADJSTART, 0x8765); // Checks correctness of 31-3A registers and starts normal measurement if ok - uint16_t sys_status = this->read16_(ATM90E26_REGISTER_SYSSTATUS); + const uint16_t sys_status = this->read16_(ATM90E26_REGISTER_SYSSTATUS); if (sys_status & 0xC000) { // Checksum 1 Error ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS1 was incorrect, expected: 0x%04X", @@ -177,27 +177,27 @@ void ATM90E26Component::write16_(uint8_t a_register, uint16_t val) { } float ATM90E26Component::get_line_current_() { - uint16_t current = this->read16_(ATM90E26_REGISTER_IRMS); + const uint16_t current = this->read16_(ATM90E26_REGISTER_IRMS); return current / 1000.0f; } float ATM90E26Component::get_line_voltage_() { - uint16_t voltage = this->read16_(ATM90E26_REGISTER_URMS); + const uint16_t voltage = this->read16_(ATM90E26_REGISTER_URMS); return voltage / 100.0f; } float ATM90E26Component::get_active_power_() { - int16_t val = this->read16_(ATM90E26_REGISTER_PMEAN); // two's complement + const int16_t val = this->read16_(ATM90E26_REGISTER_PMEAN); // two's complement return (float) val; } float ATM90E26Component::get_reactive_power_() { - int16_t val = this->read16_(ATM90E26_REGISTER_QMEAN); // two's complement + const int16_t val = this->read16_(ATM90E26_REGISTER_QMEAN); // two's complement return (float) val; } float ATM90E26Component::get_power_factor_() { - uint16_t val = this->read16_(ATM90E26_REGISTER_POWERF); // signed + const uint16_t val = this->read16_(ATM90E26_REGISTER_POWERF); // signed if (val & 0x8000) { return -(val & 0x7FF) / 1000.0f; } else { @@ -206,7 +206,7 @@ float ATM90E26Component::get_power_factor_() { } float ATM90E26Component::get_forward_active_energy_() { - uint16_t val = this->read16_(ATM90E26_REGISTER_APENERGY); + const uint16_t val = this->read16_(ATM90E26_REGISTER_APENERGY); if ((UINT32_MAX - this->cumulative_forward_active_energy_) > val) { this->cumulative_forward_active_energy_ += val; } else { @@ -217,7 +217,7 @@ float ATM90E26Component::get_forward_active_energy_() { } float ATM90E26Component::get_reverse_active_energy_() { - uint16_t val = this->read16_(ATM90E26_REGISTER_ANENERGY); + const uint16_t val = this->read16_(ATM90E26_REGISTER_ANENERGY); if (UINT32_MAX - this->cumulative_reverse_active_energy_ > val) { this->cumulative_reverse_active_energy_ += val; } else { @@ -227,7 +227,7 @@ float ATM90E26Component::get_reverse_active_energy_() { } float ATM90E26Component::get_frequency_() { - uint16_t freq = this->read16_(ATM90E26_REGISTER_FREQ); + const uint16_t freq = this->read16_(ATM90E26_REGISTER_FREQ); return freq / 100.0f; } diff --git a/esphome/components/atm90e32/atm90e32.cpp b/esphome/components/atm90e32/atm90e32.cpp index e38fd3866a..e27459b18a 100644 --- a/esphome/components/atm90e32/atm90e32.cpp +++ b/esphome/components/atm90e32/atm90e32.cpp @@ -7,82 +7,128 @@ namespace esphome { namespace atm90e32 { static const char *const TAG = "atm90e32"; +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) { + this->phase_[phase].voltage_ = this->get_phase_voltage_(phase); + } + } + for (uint8_t phase = 0; phase < 3; phase++) { + if (this->phase_[phase].current_sensor_ != nullptr) { + this->phase_[phase].current_ = this->get_phase_current_(phase); + } + } + 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); + } + } + 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); + } + } + 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); + } + } + 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); + } + } + 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); + } + } + 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); + } + } + 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); + } + } + 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 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)); + } + } + 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)); + } + } + 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)); + } + } + 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)); + } + } + 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)); + } + } + 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)); + } + } + 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)); + } + } + 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) { + this->freq_sensor_->publish_state(this->get_frequency_()); + } + 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) { this->status_set_warning(); return; } - - if (this->phase_[0].voltage_sensor_ != nullptr) { - this->phase_[0].voltage_sensor_->publish_state(this->get_line_voltage_a_()); - } - if (this->phase_[1].voltage_sensor_ != nullptr) { - this->phase_[1].voltage_sensor_->publish_state(this->get_line_voltage_b_()); - } - if (this->phase_[2].voltage_sensor_ != nullptr) { - this->phase_[2].voltage_sensor_->publish_state(this->get_line_voltage_c_()); - } - if (this->phase_[0].current_sensor_ != nullptr) { - this->phase_[0].current_sensor_->publish_state(this->get_line_current_a_()); - } - if (this->phase_[1].current_sensor_ != nullptr) { - this->phase_[1].current_sensor_->publish_state(this->get_line_current_b_()); - } - if (this->phase_[2].current_sensor_ != nullptr) { - this->phase_[2].current_sensor_->publish_state(this->get_line_current_c_()); - } - if (this->phase_[0].power_sensor_ != nullptr) { - this->phase_[0].power_sensor_->publish_state(this->get_active_power_a_()); - } - if (this->phase_[1].power_sensor_ != nullptr) { - this->phase_[1].power_sensor_->publish_state(this->get_active_power_b_()); - } - if (this->phase_[2].power_sensor_ != nullptr) { - this->phase_[2].power_sensor_->publish_state(this->get_active_power_c_()); - } - if (this->phase_[0].reactive_power_sensor_ != nullptr) { - this->phase_[0].reactive_power_sensor_->publish_state(this->get_reactive_power_a_()); - } - if (this->phase_[1].reactive_power_sensor_ != nullptr) { - this->phase_[1].reactive_power_sensor_->publish_state(this->get_reactive_power_b_()); - } - if (this->phase_[2].reactive_power_sensor_ != nullptr) { - this->phase_[2].reactive_power_sensor_->publish_state(this->get_reactive_power_c_()); - } - if (this->phase_[0].power_factor_sensor_ != nullptr) { - this->phase_[0].power_factor_sensor_->publish_state(this->get_power_factor_a_()); - } - if (this->phase_[1].power_factor_sensor_ != nullptr) { - this->phase_[1].power_factor_sensor_->publish_state(this->get_power_factor_b_()); - } - if (this->phase_[2].power_factor_sensor_ != nullptr) { - this->phase_[2].power_factor_sensor_->publish_state(this->get_power_factor_c_()); - } - if (this->phase_[0].forward_active_energy_sensor_ != nullptr) { - this->phase_[0].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_a_()); - } - if (this->phase_[1].forward_active_energy_sensor_ != nullptr) { - this->phase_[1].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_b_()); - } - if (this->phase_[2].forward_active_energy_sensor_ != nullptr) { - this->phase_[2].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_c_()); - } - if (this->phase_[0].reverse_active_energy_sensor_ != nullptr) { - this->phase_[0].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_a_()); - } - if (this->phase_[1].reverse_active_energy_sensor_ != nullptr) { - this->phase_[1].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_b_()); - } - if (this->phase_[2].reverse_active_energy_sensor_ != nullptr) { - this->phase_[2].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_c_()); - } - if (this->freq_sensor_ != nullptr) { - this->freq_sensor_->publish_state(this->get_frequency_()); - } - if (this->chip_temperature_sensor_ != nullptr) { - this->chip_temperature_sensor_->publish_state(this->get_chip_temperature_()); - } + this->set_publish_interval_flag_(true); this->status_clear_warning(); } @@ -101,29 +147,51 @@ 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 - this->write16_(ATM90E32_REGISTER_METEREN, 0x0001); // Enable Metering - if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != 0x0001) { + 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_PLCONSTH, 0x0861); // PL Constant MSB (default) = 140625000 - this->write16_(ATM90E32_REGISTER_PLCONSTL, 0xC468); // PL Constant LSB (default) - 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_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_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750 - this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10% - this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[0].volt_gain_); // A Voltage rms gain - this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[0].ct_gain_); // A line current gain - this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[1].volt_gain_); // B Voltage rms gain - this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[1].ct_gain_); // B line current gain - this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[2].volt_gain_); // C Voltage rms gain - this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[2].ct_gain_); // C line current gain - this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration + + this->write16_(ATM90E32_REGISTER_METEREN, 0x0001); // Enable Metering + 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); // 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_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% + // Setup voltage and current calibration offsets for PHASE A + this->phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA); + this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // A Voltage offset + this->phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA); + this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // A Current offset + // 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 calibration offsets for PHASE B + this->phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB); + this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // B Voltage offset + this->phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB); + this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // B Current offset + // 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 calibration offsets for PHASE C + this->phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC); + this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset + this->phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC); + this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset + // 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 } void ATM90E32Component::dump_config() { @@ -133,43 +201,54 @@ void ATM90E32Component::dump_config() { ESP_LOGE(TAG, "Communication with ATM90E32 failed!"); } LOG_UPDATE_INTERVAL(this); - LOG_SENSOR(" ", "Voltage A", this->phase_[0].voltage_sensor_); - LOG_SENSOR(" ", "Current A", this->phase_[0].current_sensor_); - LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_); - LOG_SENSOR(" ", "Reactive Power A", this->phase_[0].reactive_power_sensor_); - LOG_SENSOR(" ", "PF A", this->phase_[0].power_factor_sensor_); - LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[0].forward_active_energy_sensor_); - LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[0].reverse_active_energy_sensor_); - LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_); - LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_); - LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_); - LOG_SENSOR(" ", "Reactive Power B", this->phase_[1].reactive_power_sensor_); - LOG_SENSOR(" ", "PF B", this->phase_[1].power_factor_sensor_); - LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[1].forward_active_energy_sensor_); - LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[1].reverse_active_energy_sensor_); - LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_); - LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_); - LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_); - LOG_SENSOR(" ", "Reactive Power C", this->phase_[2].reactive_power_sensor_); - LOG_SENSOR(" ", "PF C", this->phase_[2].power_factor_sensor_); - LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[2].forward_active_energy_sensor_); - LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[2].reverse_active_energy_sensor_); + LOG_SENSOR(" ", "Voltage A", this->phase_[PHASEA].voltage_sensor_); + 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(" ", "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_); + LOG_SENSOR(" ", "Harmonic Power A", this->phase_[PHASEA].harmonic_active_power_sensor_); + LOG_SENSOR(" ", "Phase Angle A", this->phase_[PHASEA].phase_angle_sensor_); + LOG_SENSOR(" ", "Peak Current A", this->phase_[PHASEA].peak_current_sensor_); + LOG_SENSOR(" ", "Voltage B", this->phase_[PHASEB].voltage_sensor_); + 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(" ", "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 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(" ", "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 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_); } -float ATM90E32Component::get_setup_priority() const { return setup_priority::DATA; } +float ATM90E32Component::get_setup_priority() const { return setup_priority::IO; } + +// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H) +// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period +// Default is 143FH (20ms, 63ms) uint16_t ATM90E32Component::read16_(uint16_t a_register) { uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03); uint8_t addrl = (a_register & 0xFF); uint8_t data[2]; uint16_t output; - this->enable(); - delayMicroseconds(10); + delay_microseconds_safe(10); this->write_byte(addrh); this->write_byte(addrl); - delayMicroseconds(4); this->read_array(data, 2); this->disable(); @@ -179,9 +258,9 @@ uint16_t ATM90E32Component::read16_(uint16_t a_register) { } int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) { - uint16_t val_h = this->read16_(addr_h); - uint16_t val_l = this->read16_(addr_l); - int32_t val = (val_h << 16) | val_l; + const uint16_t val_h = this->read16_(addr_h); + const uint16_t val_l = this->read16_(addr_l); + const int32_t val = (val_h << 16) | val_l; ESP_LOGVV(TAG, "read32_ addr_h 0x%04" PRIX16 " val_h 0x%04" PRIX16 " addr_l 0x%04" PRIX16 " val_l 0x%04" PRIX16 @@ -192,141 +271,174 @@ int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) { } void ATM90E32Component::write16_(uint16_t a_register, uint16_t val) { - uint8_t addrh = (a_register >> 8) & 0x03; - uint8_t addrl = (a_register & 0xFF); - ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val); this->enable(); - delayMicroseconds(10); - this->write_byte(addrh); - this->write_byte(addrl); - delayMicroseconds(4); - this->write_byte((val >> 8) & 0xff); - this->write_byte(val & 0xFF); + this->write_byte16(a_register); + this->write_byte16(val); this->disable(); + if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != val) + ESP_LOGW(TAG, "SPI write error 0x%04X val 0x%04X", a_register, val); } -float ATM90E32Component::get_line_voltage_a_() { - uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSA); +float ATM90E32Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; } + +float ATM90E32Component::get_local_phase_current_(uint8_t phase) { return this->phase_[phase].current_; } + +float ATM90E32Component::get_local_phase_active_power_(uint8_t phase) { return this->phase_[phase].active_power_; } + +float ATM90E32Component::get_local_phase_reactive_power_(uint8_t phase) { return this->phase_[phase].reactive_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) { + return this->phase_[phase].forward_active_energy_; +} + +float ATM90E32Component::get_local_phase_reverse_active_energy_(uint8_t phase) { + return this->phase_[phase].reverse_active_energy_; +} + +float ATM90E32Component::get_local_phase_angle_(uint8_t phase) { return this->phase_[phase].phase_angle_; } + +float ATM90E32Component::get_local_phase_harmonic_active_power_(uint8_t phase) { + return this->phase_[phase].harmonic_active_power_; +} + +float ATM90E32Component::get_local_phase_peak_current_(uint8_t phase) { return this->phase_[phase].peak_current_; } + +float ATM90E32Component::get_phase_voltage_(uint8_t phase) { + const uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMS + phase); + if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != voltage) + ESP_LOGW(TAG, "SPI URMS voltage register read error."); return (float) voltage / 100; } -float ATM90E32Component::get_line_voltage_b_() { - uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSB); - return (float) voltage / 100; + +float ATM90E32Component::get_phase_voltage_avg_(uint8_t phase) { + const uint8_t reads = 10; + uint32_t accumulation = 0; + uint16_t voltage = 0; + for (uint8_t i = 0; i < reads; i++) { + voltage = this->read16_(ATM90E32_REGISTER_URMS + phase); + if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != voltage) + ESP_LOGW(TAG, "SPI URMS voltage register read error."); + accumulation += voltage; + } + voltage = accumulation / reads; + this->phase_[phase].voltage_ = (float) voltage / 100; + return this->phase_[phase].voltage_; } -float ATM90E32Component::get_line_voltage_c_() { - uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSC); - return (float) voltage / 100; + +float ATM90E32Component::get_phase_current_avg_(uint8_t phase) { + const uint8_t reads = 10; + uint32_t accumulation = 0; + uint16_t current = 0; + for (uint8_t i = 0; i < reads; i++) { + current = this->read16_(ATM90E32_REGISTER_IRMS + phase); + if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != current) + ESP_LOGW(TAG, "SPI IRMS current register read error."); + accumulation += current; + } + current = accumulation / reads; + this->phase_[phase].current_ = (float) current / 1000; + return this->phase_[phase].current_; } -float ATM90E32Component::get_line_current_a_() { - uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSA); + +float ATM90E32Component::get_phase_current_(uint8_t phase) { + const uint16_t current = this->read16_(ATM90E32_REGISTER_IRMS + phase); + if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != current) + ESP_LOGW(TAG, "SPI IRMS current register read error."); return (float) current / 1000; } -float ATM90E32Component::get_line_current_b_() { - uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSB); - return (float) current / 1000; -} -float ATM90E32Component::get_line_current_c_() { - uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSC); - return (float) current / 1000; -} -float ATM90E32Component::get_active_power_a_() { - int val = this->read32_(ATM90E32_REGISTER_PMEANA, ATM90E32_REGISTER_PMEANALSB); + +float ATM90E32Component::get_phase_active_power_(uint8_t phase) { + const int val = this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase); return val * 0.00032f; } -float ATM90E32Component::get_active_power_b_() { - int val = this->read32_(ATM90E32_REGISTER_PMEANB, ATM90E32_REGISTER_PMEANBLSB); + +float ATM90E32Component::get_phase_reactive_power_(uint8_t phase) { + const int val = this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase); return val * 0.00032f; } -float ATM90E32Component::get_active_power_c_() { - int val = this->read32_(ATM90E32_REGISTER_PMEANC, ATM90E32_REGISTER_PMEANCLSB); + +float ATM90E32Component::get_phase_power_factor_(uint8_t phase) { + 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) { + const uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGY + phase); + if ((UINT32_MAX - this->phase_[phase].cumulative_forward_active_energy_) > val) { + this->phase_[phase].cumulative_forward_active_energy_ += val; + } else { + this->phase_[phase].cumulative_forward_active_energy_ = val; + } + 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); + 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; + } + return ((float) this->phase_[phase].cumulative_reverse_active_energy_ * 10 / 3200); +} + +float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) { + int val = this->read32_(ATM90E32_REGISTER_PMEANH + phase, ATM90E32_REGISTER_PMEANHLSB + phase); return val * 0.00032f; } -float ATM90E32Component::get_reactive_power_a_() { - int val = this->read32_(ATM90E32_REGISTER_QMEANA, ATM90E32_REGISTER_QMEANALSB); - return val * 0.00032f; + +float ATM90E32Component::get_phase_angle_(uint8_t phase) { + uint16_t val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0; + return (float) (val > 180) ? val - 360.0 : val; } -float ATM90E32Component::get_reactive_power_b_() { - int val = this->read32_(ATM90E32_REGISTER_QMEANB, ATM90E32_REGISTER_QMEANBLSB); - return val * 0.00032f; -} -float ATM90E32Component::get_reactive_power_c_() { - int val = this->read32_(ATM90E32_REGISTER_QMEANC, ATM90E32_REGISTER_QMEANCLSB); - return val * 0.00032f; -} -float ATM90E32Component::get_power_factor_a_() { - int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANA); - return (float) pf / 1000; -} -float ATM90E32Component::get_power_factor_b_() { - int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANB); - return (float) pf / 1000; -} -float ATM90E32Component::get_power_factor_c_() { - int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANC); - return (float) pf / 1000; -} -float ATM90E32Component::get_forward_active_energy_a_() { - uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA); - if ((UINT32_MAX - this->phase_[0].cumulative_forward_active_energy_) > val) { - this->phase_[0].cumulative_forward_active_energy_ += val; - } else { - this->phase_[0].cumulative_forward_active_energy_ = val; - } - return ((float) this->phase_[0].cumulative_forward_active_energy_ * 10 / 3200); -} -float ATM90E32Component::get_forward_active_energy_b_() { - uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB); - if (UINT32_MAX - this->phase_[1].cumulative_forward_active_energy_ > val) { - this->phase_[1].cumulative_forward_active_energy_ += val; - } else { - this->phase_[1].cumulative_forward_active_energy_ = val; - } - return ((float) this->phase_[1].cumulative_forward_active_energy_ * 10 / 3200); -} -float ATM90E32Component::get_forward_active_energy_c_() { - uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC); - if (UINT32_MAX - this->phase_[2].cumulative_forward_active_energy_ > val) { - this->phase_[2].cumulative_forward_active_energy_ += val; - } else { - this->phase_[2].cumulative_forward_active_energy_ = val; - } - return ((float) this->phase_[2].cumulative_forward_active_energy_ * 10 / 3200); -} -float ATM90E32Component::get_reverse_active_energy_a_() { - uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA); - if (UINT32_MAX - this->phase_[0].cumulative_reverse_active_energy_ > val) { - this->phase_[0].cumulative_reverse_active_energy_ += val; - } else { - this->phase_[0].cumulative_reverse_active_energy_ = val; - } - return ((float) this->phase_[0].cumulative_reverse_active_energy_ * 10 / 3200); -} -float ATM90E32Component::get_reverse_active_energy_b_() { - uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB); - if (UINT32_MAX - this->phase_[1].cumulative_reverse_active_energy_ > val) { - this->phase_[1].cumulative_reverse_active_energy_ += val; - } else { - this->phase_[1].cumulative_reverse_active_energy_ = val; - } - return ((float) this->phase_[1].cumulative_reverse_active_energy_ * 10 / 3200); -} -float ATM90E32Component::get_reverse_active_energy_c_() { - uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC); - if (UINT32_MAX - this->phase_[2].cumulative_reverse_active_energy_ > val) { - this->phase_[2].cumulative_reverse_active_energy_ += val; - } else { - this->phase_[2].cumulative_reverse_active_energy_ = val; - } - return ((float) this->phase_[2].cumulative_reverse_active_energy_ * 10 / 3200); + +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 = abs(val); + // phase register * phase current gain value / 1000 * 2^13 + return (float) (val * this->phase_[phase].ct_gain_ / 8192000.0); } + float ATM90E32Component::get_frequency_() { - uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ); + const uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ); return (float) freq / 100; } + float ATM90E32Component::get_chip_temperature_() { - uint16_t ctemp = this->read16_(ATM90E32_REGISTER_TEMP); + const uint16_t ctemp = this->read16_(ATM90E32_REGISTER_TEMP); return (float) ctemp; } + +uint16_t ATM90E32Component::calibrate_voltage_offset_phase(uint8_t phase) { + const uint8_t num_reads = 5; + uint64_t total_value = 0; + 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_value = average_value >> 7; + const uint32_t voltage_offset = ~shifted_value + 1; + return voltage_offset & 0xFFFF; // Take the lower 16 bits +} + +uint16_t ATM90E32Component::calibrate_current_offset_phase(uint8_t phase) { + const uint8_t num_reads = 5; + uint64_t total_value = 0; + 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 current_offset = ~average_value + 1; + return current_offset & 0xFFFF; // Take the lower 16 bits +} + } // namespace atm90e32 } // namespace esphome diff --git a/esphome/components/atm90e32/atm90e32.h b/esphome/components/atm90e32/atm90e32.h index c9662df26e..0a334dbe8b 100644 --- a/esphome/components/atm90e32/atm90e32.h +++ b/esphome/components/atm90e32/atm90e32.h @@ -3,14 +3,19 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/spi/spi.h" +#include "atm90e32_reg.h" namespace esphome { namespace atm90e32 { class ATM90E32Component : public PollingComponent, public spi::SPIDevice { + spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_1MHZ> { public: + static const uint8_t PHASEA = 0; + static const uint8_t PHASEB = 1; + static const uint8_t PHASEC = 2; + void loop() override; void setup() override; void dump_config() override; float get_setup_priority() const override; @@ -20,6 +25,7 @@ class ATM90E32Component : public PollingComponent, void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; } void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; } void set_reactive_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].reactive_power_sensor_ = obj; } + void set_apparent_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].apparent_power_sensor_ = obj; } void set_forward_active_energy_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].forward_active_energy_sensor_ = obj; } @@ -27,64 +33,94 @@ class ATM90E32Component : public PollingComponent, this->phase_[phase].reverse_active_energy_sensor_ = obj; } void set_power_factor_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_factor_sensor_ = obj; } - void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; } + void set_phase_angle_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].phase_angle_sensor_ = obj; } + void set_harmonic_active_power_sensor(int phase, sensor::Sensor *obj) { + this->phase_[phase].harmonic_active_power_sensor_ = obj; + } + 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_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) { chip_temperature_sensor_ = chip_temperature_sensor; } void set_line_freq(int freq) { line_freq_ = freq; } void set_current_phases(int phases) { current_phases_ = phases; } void set_pga_gain(uint16_t gain) { pga_gain_ = gain; } + 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: 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_line_voltage_a_(); - float get_line_voltage_b_(); - float get_line_voltage_c_(); - float get_line_current_a_(); - float get_line_current_b_(); - float get_line_current_c_(); - float get_active_power_a_(); - float get_active_power_b_(); - float get_active_power_c_(); - float get_reactive_power_a_(); - float get_reactive_power_b_(); - float get_reactive_power_c_(); - float get_power_factor_a_(); - float get_power_factor_b_(); - float get_power_factor_c_(); - float get_forward_active_energy_a_(); - float get_forward_active_energy_b_(); - float get_forward_active_energy_c_(); - float get_reverse_active_energy_a_(); - float get_reverse_active_energy_b_(); - float get_reverse_active_energy_c_(); + 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; }; struct ATM90E32Phase { - uint16_t volt_gain_{7305}; + uint16_t voltage_gain_{7305}; uint16_t ct_gain_{27961}; + 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 power_factor_{0}; + float forward_active_energy_{0}; + float reverse_active_energy_{0}; + float phase_angle_{0}; + float harmonic_active_power_{0}; + float peak_current_{0}; sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr}; sensor::Sensor *reactive_power_sensor_{nullptr}; + sensor::Sensor *apparent_power_sensor_{nullptr}; sensor::Sensor *power_factor_sensor_{nullptr}; sensor::Sensor *forward_active_energy_sensor_{nullptr}; sensor::Sensor *reverse_active_energy_sensor_{nullptr}; + sensor::Sensor *phase_angle_sensor_{nullptr}; + sensor::Sensor *harmonic_active_power_sensor_{nullptr}; + sensor::Sensor *peak_current_sensor_{nullptr}; uint32_t cumulative_forward_active_energy_{0}; uint32_t cumulative_reverse_active_energy_{0}; } phase_[3]; + sensor::Sensor *freq_sensor_{nullptr}; sensor::Sensor *chip_temperature_sensor_{nullptr}; uint16_t pga_gain_{0x15}; int line_freq_{60}; int current_phases_{3}; + bool publish_interval_flag_{true}; + bool peak_current_signed_{false}; }; } // namespace atm90e32 diff --git a/esphome/components/atm90e32/atm90e32_reg.h b/esphome/components/atm90e32/atm90e32_reg.h index 7927a7fdfb..dac62aa6b4 100644 --- a/esphome/components/atm90e32/atm90e32_reg.h +++ b/esphome/components/atm90e32/atm90e32_reg.h @@ -131,10 +131,12 @@ static const uint16_t ATM90E32_REGISTER_IOFFSETN = 0x6E; // N Current Offset /* ENERGY REGISTERS */ static const uint16_t ATM90E32_REGISTER_APENERGYT = 0x80; // Total Forward Active +static const uint16_t ATM90E32_REGISTER_APENERGY = 0x81; // Forward Active Reg Base static const uint16_t ATM90E32_REGISTER_APENERGYA = 0x81; // A Forward Active static const uint16_t ATM90E32_REGISTER_APENERGYB = 0x82; // B Forward Active static const uint16_t ATM90E32_REGISTER_APENERGYC = 0x83; // C Forward Active static const uint16_t ATM90E32_REGISTER_ANENERGYT = 0x84; // Total Reverse Active +static const uint16_t ATM90E32_REGISTER_ANENERGY = 0x85; // Reverse Active Reg Base static const uint16_t ATM90E32_REGISTER_ANENERGYA = 0x85; // A Reverse Active static const uint16_t ATM90E32_REGISTER_ANENERGYB = 0x86; // B Reverse Active static const uint16_t ATM90E32_REGISTER_ANENERGYC = 0x87; // C Reverse Active @@ -172,10 +174,12 @@ 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; // 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; // 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) @@ -184,15 +188,18 @@ 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) static const uint16_t ATM90E32_REGISTER_PFMEANT = 0xBC; // Mean Power Factor +static const uint16_t ATM90E32_REGISTER_PFMEAN = 0xBD; // Power Factor Reg Base static const uint16_t ATM90E32_REGISTER_PFMEANA = 0xBD; // A Power Factor static const uint16_t ATM90E32_REGISTER_PFMEANB = 0xBE; // B Power Factor static const uint16_t ATM90E32_REGISTER_PFMEANC = 0xBF; // C Power Factor static const uint16_t ATM90E32_REGISTER_PMEANTLSB = 0xC0; // Lower Word (Tot. Act. Power) +static const uint16_t ATM90E32_REGISTER_PMEANLSB = 0xC1; // Lower Word Reg Base (Active Power) static const uint16_t ATM90E32_REGISTER_PMEANALSB = 0xC1; // Lower Word (A Act. Power) static const uint16_t ATM90E32_REGISTER_PMEANBLSB = 0xC2; // Lower Word (B Act. Power) static const uint16_t ATM90E32_REGISTER_PMEANCLSB = 0xC3; // Lower Word (C Act. Power) static const uint16_t ATM90E32_REGISTER_QMEANTLSB = 0xC4; // Lower Word (Tot. React. Power) +static const uint16_t ATM90E32_REGISTER_QMEANLSB = 0xC5; // Lower Word Reg Base (Reactive Power) static const uint16_t ATM90E32_REGISTER_QMEANALSB = 0xC5; // Lower Word (A React. Power) 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) @@ -207,12 +214,15 @@ static const uint16_t ATM90E32_REGISTER_PMEANAF = 0xD1; // A Active Fund. Power static const uint16_t ATM90E32_REGISTER_PMEANBF = 0xD2; // B Active Fund. Power static const uint16_t ATM90E32_REGISTER_PMEANCF = 0xD3; // C Active Fund. Power static const uint16_t ATM90E32_REGISTER_PMEANTH = 0xD4; // Total Active Harm. Power +static const uint16_t ATM90E32_REGISTER_PMEANH = 0xD5; // Active Harm. Power Reg Base static const uint16_t ATM90E32_REGISTER_PMEANAH = 0xD5; // A Active Harm. Power static const uint16_t ATM90E32_REGISTER_PMEANBH = 0xD6; // B Active Harm. Power static const uint16_t ATM90E32_REGISTER_PMEANCH = 0xD7; // C Active Harm. Power +static const uint16_t ATM90E32_REGISTER_URMS = 0xD9; // RMS Voltage Reg Base static const uint16_t ATM90E32_REGISTER_URMSA = 0xD9; // A RMS Voltage static const uint16_t ATM90E32_REGISTER_URMSB = 0xDA; // B RMS Voltage static const uint16_t ATM90E32_REGISTER_URMSC = 0xDB; // C RMS Voltage +static const uint16_t ATM90E32_REGISTER_IRMS = 0xDD; // RMS Current Reg Base static const uint16_t ATM90E32_REGISTER_IRMSA = 0xDD; // A RMS Current static const uint16_t ATM90E32_REGISTER_IRMSB = 0xDE; // B RMS Current static const uint16_t ATM90E32_REGISTER_IRMSC = 0xDF; // C RMS Current @@ -223,12 +233,15 @@ static const uint16_t ATM90E32_REGISTER_PMEANAFLSB = 0xE1; // Lower Word (A Act static const uint16_t ATM90E32_REGISTER_PMEANBFLSB = 0xE2; // Lower Word (B Act. Fund. Power) static const uint16_t ATM90E32_REGISTER_PMEANCFLSB = 0xE3; // Lower Word (C Act. Fund. Power) static const uint16_t ATM90E32_REGISTER_PMEANTHLSB = 0xE4; // Lower Word (Tot. Act. Harm. Power) +static const uint16_t ATM90E32_REGISTER_PMEANHLSB = 0xE5; // Lower Word (A Act. Harm. Power) Reg Base static const uint16_t ATM90E32_REGISTER_PMEANAHLSB = 0xE5; // Lower Word (A Act. Harm. Power) static const uint16_t ATM90E32_REGISTER_PMEANBHLSB = 0xE6; // Lower Word (B Act. Harm. Power) static const uint16_t ATM90E32_REGISTER_PMEANCHLSB = 0xE7; // Lower Word (C Act. Harm. Power) +static const uint16_t ATM90E32_REGISTER_URMSLSB = 0xE9; // Lower Word RMS Voltage Reg Base static const uint16_t ATM90E32_REGISTER_URMSALSB = 0xE9; // Lower Word (A RMS Voltage) static const uint16_t ATM90E32_REGISTER_URMSBLSB = 0xEA; // Lower Word (B RMS Voltage) static const uint16_t ATM90E32_REGISTER_URMSCLSB = 0xEB; // Lower Word (C RMS Voltage) +static const uint16_t ATM90E32_REGISTER_IRMSLSB = 0xED; // Lower Word RMS Current Reg Base static const uint16_t ATM90E32_REGISTER_IRMSALSB = 0xED; // Lower Word (A RMS Current) static const uint16_t ATM90E32_REGISTER_IRMSBLSB = 0xEE; // Lower Word (B RMS Current) static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C RMS Current) @@ -237,10 +250,12 @@ static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C RMS static const uint16_t ATM90E32_REGISTER_UPEAKA = 0xF1; // A Voltage Peak static const uint16_t ATM90E32_REGISTER_UPEAKB = 0xF2; // B Voltage Peak static const uint16_t ATM90E32_REGISTER_UPEAKC = 0xF3; // C Voltage Peak +static const uint16_t ATM90E32_REGISTER_IPEAK = 0xF5; // Peak Current Reg Base static const uint16_t ATM90E32_REGISTER_IPEAKA = 0xF5; // A Current Peak static const uint16_t ATM90E32_REGISTER_IPEAKB = 0xF6; // B Current Peak static const uint16_t ATM90E32_REGISTER_IPEAKC = 0xF7; // C Current Peak static const uint16_t ATM90E32_REGISTER_FREQ = 0xF8; // Frequency +static const uint16_t ATM90E32_REGISTER_PANGLE = 0xF9; // Mean Phase Angle Reg Base static const uint16_t ATM90E32_REGISTER_PANGLEA = 0xF9; // A Mean Phase Angle static const uint16_t ATM90E32_REGISTER_PANGLEB = 0xFA; // B Mean Phase Angle static const uint16_t ATM90E32_REGISTER_PANGLEC = 0xFB; // C Mean Phase Angle diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index af4d2ef412..2bc7f0498d 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -9,8 +9,10 @@ from esphome.const import ( CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C, + CONF_PHASE_ANGLE, CONF_POWER, CONF_POWER_FACTOR, + CONF_APPARENT_POWER, CONF_FREQUENCY, CONF_FORWARD_ACTIVE_ENERGY, CONF_REVERSE_ACTIVE_ENERGY, @@ -25,12 +27,13 @@ from esphome.const import ( ICON_CURRENT_AC, STATE_CLASS_MEASUREMENT, STATE_CLASS_TOTAL_INCREASING, + UNIT_AMPERE, + UNIT_DEGREES, + UNIT_CELSIUS, UNIT_HERTZ, UNIT_VOLT, - UNIT_AMPERE, - UNIT_WATT, - UNIT_CELSIUS, UNIT_VOLT_AMPS_REACTIVE, + UNIT_WATT, UNIT_WATT_HOURS, ) @@ -40,6 +43,10 @@ CONF_GAIN_PGA = "gain_pga" CONF_CURRENT_PHASES = "current_phases" CONF_GAIN_VOLTAGE = "gain_voltage" CONF_GAIN_CT = "gain_ct" +CONF_HARMONIC_POWER = "harmonic_power" +CONF_PEAK_CURRENT = "peak_current" +CONF_PEAK_CURRENT_SIGNED = "peak_current_signed" +UNIT_DEG = "degrees" LINE_FREQS = { "50HZ": 50, "60HZ": 60, @@ -85,6 +92,12 @@ ATM90E32_PHASE_SCHEMA = cv.Schema( accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( accuracy_decimals=2, device_class=DEVICE_CLASS_POWER_FACTOR, @@ -102,6 +115,24 @@ ATM90E32_PHASE_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional(CONF_PHASE_ANGLE): sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREES, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HARMONIC_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PEAK_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, } @@ -132,6 +163,7 @@ CONFIG_SCHEMA = ( CURRENT_PHASES, 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, } ) .extend(cv.polling_component_schema("60s")) @@ -162,6 +194,9 @@ async def to_code(config): if reactive_power_config := conf.get(CONF_REACTIVE_POWER): sens = await sensor.new_sensor(reactive_power_config) cg.add(var.set_reactive_power_sensor(i, sens)) + if apparent_power_config := conf.get(CONF_APPARENT_POWER): + sens = await sensor.new_sensor(apparent_power_config) + cg.add(var.set_apparent_power_sensor(i, sens)) if power_factor_config := conf.get(CONF_POWER_FACTOR): sens = await sensor.new_sensor(power_factor_config) cg.add(var.set_power_factor_sensor(i, sens)) @@ -171,6 +206,15 @@ async def to_code(config): if reverse_active_energy_config := conf.get(CONF_REVERSE_ACTIVE_ENERGY): sens = await sensor.new_sensor(reverse_active_energy_config) cg.add(var.set_reverse_active_energy_sensor(i, sens)) + if phase_angle_config := conf.get(CONF_PHASE_ANGLE): + sens = await sensor.new_sensor(phase_angle_config) + cg.add(var.set_phase_angle_sensor(i, sens)) + if harmonic_active_power_config := conf.get(CONF_HARMONIC_POWER): + sens = await sensor.new_sensor(harmonic_active_power_config) + cg.add(var.set_harmonic_active_power_sensor(i, sens)) + 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) @@ -182,3 +226,4 @@ async def to_code(config): 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])) diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index 13b0cd2a09..b34764907f 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -194,8 +194,8 @@ void BangBangClimate::dump_config() { ESP_LOGCONFIG(TAG, " Supports HEAT: %s", YESNO(this->supports_heat_)); ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_)); ESP_LOGCONFIG(TAG, " Supports AWAY mode: %s", YESNO(this->supports_away_)); - ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C", this->normal_config_.default_temperature_low); - ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature_high); + ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.2f°C", this->normal_config_.default_temperature_low); + ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.2f°C", this->normal_config_.default_temperature_high); } BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig() = default; diff --git a/esphome/components/bedjet/bedjet_hub.cpp b/esphome/components/bedjet/bedjet_hub.cpp index 7933a35a97..6404298697 100644 --- a/esphome/components/bedjet/bedjet_hub.cpp +++ b/esphome/components/bedjet/bedjet_hub.cpp @@ -242,7 +242,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga this->set_notify_(true); #ifdef USE_TIME - if (this->time_id_.has_value()) { + if (this->time_id_ != nullptr) { this->send_local_time(); } #endif @@ -441,9 +441,8 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) { #ifdef USE_TIME void BedJetHub::send_local_time() { - if (this->time_id_.has_value()) { - auto *time_id = *this->time_id_; - ESPTime now = time_id->now(); + if (this->time_id_ != nullptr) { + ESPTime now = this->time_id_->now(); if (now.is_valid()) { this->set_clock(now.hour, now.minute); ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute); @@ -454,10 +453,9 @@ void BedJetHub::send_local_time() { } void BedJetHub::setup_time_() { - if (this->time_id_.has_value()) { + if (this->time_id_ != nullptr) { this->send_local_time(); - auto *time_id = *this->time_id_; - time_id->add_on_time_sync_callback([this] { this->send_local_time(); }); + this->time_id_->add_on_time_sync_callback([this] { this->send_local_time(); }); } else { ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock."); } diff --git a/esphome/components/bedjet/bedjet_hub.h b/esphome/components/bedjet/bedjet_hub.h index bb1349b2ac..6258795b02 100644 --- a/esphome/components/bedjet/bedjet_hub.h +++ b/esphome/components/bedjet/bedjet_hub.h @@ -141,7 +141,7 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo #ifdef USE_TIME /** Initializes time sync callbacks to support syncing current time to the BedJet. */ void setup_time_(); - optional time_id_{}; + time::RealTimeClock *time_id_{nullptr}; #endif uint32_t timeout_{DEFAULT_STATUS_TIMEOUT}; diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index babe46e082..2f788d7103 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -141,6 +141,7 @@ DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Compon InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter) AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component) LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter) +SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component) FILTER_REGISTRY = Registry() validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) @@ -259,6 +260,19 @@ async def lambda_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id, lambda_) +@register_filter( + "settle", + SettleFilter, + cv.templatable(cv.positive_time_period_milliseconds), +) +async def settle_filter_to_code(config, filter_id): + var = cg.new_Pvariable(filter_id) + await cg.register_component(var, {}) + template_ = await cg.templatable(config, [], cg.uint32) + cg.add(var.set_delay(template_)) + return var + + MULTI_CLICK_TIMING_SCHEMA = cv.Schema( { cv.Optional(CONF_STATE): cv.boolean, diff --git a/esphome/components/binary_sensor/filter.cpp b/esphome/components/binary_sensor/filter.cpp index 46957383c3..8f94b108ac 100644 --- a/esphome/components/binary_sensor/filter.cpp +++ b/esphome/components/binary_sensor/filter.cpp @@ -111,6 +111,23 @@ LambdaFilter::LambdaFilter(std::function(bool)> f) : f_(std::move optional LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); } +optional SettleFilter::new_value(bool value, bool is_initial) { + if (!this->steady_) { + this->set_timeout("SETTLE", this->delay_.value(), [this, value, is_initial]() { + this->steady_ = true; + this->output(value, is_initial); + }); + return {}; + } else { + this->steady_ = false; + this->output(value, is_initial); + this->set_timeout("SETTLE", this->delay_.value(), [this]() { this->steady_ = true; }); + return value; + } +} + +float SettleFilter::get_setup_priority() const { return setup_priority::HARDWARE; } + } // namespace binary_sensor } // namespace esphome diff --git a/esphome/components/binary_sensor/filter.h b/esphome/components/binary_sensor/filter.h index 9514cb3fe2..f7342db2fb 100644 --- a/esphome/components/binary_sensor/filter.h +++ b/esphome/components/binary_sensor/filter.h @@ -108,6 +108,19 @@ class LambdaFilter : public Filter { std::function(bool)> f_; }; +class SettleFilter : public Filter, public Component { + public: + optional new_value(bool value, bool is_initial) override; + + float get_setup_priority() const override; + + template void set_delay(T delay) { this->delay_ = delay; } + + protected: + TemplatableValue delay_{}; + bool steady_{true}; +}; + } // namespace binary_sensor } // namespace esphome diff --git a/esphome/components/bl0940/sensor.py b/esphome/components/bl0940/sensor.py index 702230e020..fc2b04f976 100644 --- a/esphome/components/bl0940/sensor.py +++ b/esphome/components/bl0940/sensor.py @@ -4,6 +4,7 @@ from esphome.components import sensor, uart from esphome.const import ( CONF_CURRENT, CONF_ENERGY, + CONF_EXTERNAL_TEMPERATURE, CONF_ID, CONF_POWER, CONF_VOLTAGE, @@ -18,12 +19,12 @@ from esphome.const import ( UNIT_KILOWATT_HOURS, UNIT_VOLT, UNIT_WATT, + STATE_CLASS_TOTAL_INCREASING, ) DEPENDENCIES = ["uart"] CONF_INTERNAL_TEMPERATURE = "internal_temperature" -CONF_EXTERNAL_TEMPERATURE = "external_temperature" bl0940_ns = cg.esphome_ns.namespace("bl0940") BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice) @@ -54,6 +55,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, diff --git a/esphome/components/bl0942/sensor.py b/esphome/components/bl0942/sensor.py index 663eea0c4d..9612df6d4c 100644 --- a/esphome/components/bl0942/sensor.py +++ b/esphome/components/bl0942/sensor.py @@ -19,6 +19,7 @@ from esphome.const import ( UNIT_VOLT, UNIT_WATT, UNIT_HERTZ, + STATE_CLASS_TOTAL_INCREASING, ) DEPENDENCIES = ["uart"] @@ -52,6 +53,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( unit_of_measurement=UNIT_HERTZ, diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 8f70ad3417..34b9868edc 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.automation import maybe_simple_id from esphome.components import esp32_ble_tracker, esp32_ble_client from esphome.const import ( CONF_CHARACTERISTIC_UUID, @@ -15,7 +16,7 @@ from esphome.const import ( from esphome import automation AUTO_LOAD = ["esp32_ble_client"] -CODEOWNERS = ["@buxtronix"] +CODEOWNERS = ["@buxtronix", "@clydebarrow"] DEPENDENCIES = ["esp32_ble_tracker"] ble_client_ns = cg.esphome_ns.namespace("ble_client") @@ -43,6 +44,10 @@ BLEClientNumericComparisonRequestTrigger = ble_client_ns.class_( # Actions BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action) +BLEConnectAction = ble_client_ns.class_("BLEClientConnectAction", automation.Action) +BLEDisconnectAction = ble_client_ns.class_( + "BLEClientDisconnectAction", automation.Action +) BLEPasskeyReplyAction = ble_client_ns.class_( "BLEClientPasskeyReplyAction", automation.Action ) @@ -58,6 +63,7 @@ CONF_ACCEPT = "accept" CONF_ON_PASSKEY_REQUEST = "on_passkey_request" CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification" CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request" +CONF_AUTO_CONNECT = "auto_connect" # Espressif platformio framework is built with MAX_BLE_CONN to 3, so # enforce this in yaml checks. @@ -69,6 +75,7 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(BLEClient), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_NAME): cv.string, + cv.Optional(CONF_AUTO_CONNECT, default=True): cv.boolean, cv.Optional(CONF_ON_CONNECT): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -135,6 +142,12 @@ BLE_WRITE_ACTION_SCHEMA = cv.Schema( } ) +BLE_CONNECT_ACTION_SCHEMA = maybe_simple_id( + { + cv.GenerateID(CONF_ID): cv.use_id(BLEClient), + } +) + BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA = cv.Schema( { cv.GenerateID(CONF_ID): cv.use_id(BLEClient), @@ -157,6 +170,24 @@ BLE_REMOVE_BOND_ACTION_SCHEMA = cv.Schema( ) +@automation.register_action( + "ble_client.disconnect", BLEDisconnectAction, BLE_CONNECT_ACTION_SCHEMA +) +async def ble_disconnect_to_code(config, action_id, template_arg, args): + parent = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, parent) + return var + + +@automation.register_action( + "ble_client.connect", BLEConnectAction, BLE_CONNECT_ACTION_SCHEMA +) +async def ble_connect_to_code(config, action_id, template_arg, args): + parent = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, parent) + return var + + @automation.register_action( "ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA ) @@ -261,6 +292,7 @@ async def to_code(config): await cg.register_component(var, config) await esp32_ble_tracker.register_client(var, config) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + cg.add(var.set_auto_connect(config[CONF_AUTO_CONNECT])) for conf in config.get(CONF_ON_CONNECT, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) diff --git a/esphome/components/ble_client/automation.cpp b/esphome/components/ble_client/automation.cpp index 429f906a5f..9a0233eb70 100644 --- a/esphome/components/ble_client/automation.cpp +++ b/esphome/components/ble_client/automation.cpp @@ -2,76 +2,10 @@ #include "automation.h" -#include -#include -#include - -#include "esphome/core/log.h" - namespace esphome { namespace ble_client { -static const char *const TAG = "ble_client.automation"; -void BLEWriterClientNode::write(const std::vector &value) { - if (this->node_state != espbt::ClientState::ESTABLISHED) { - ESP_LOGW(TAG, "Cannot write to BLE characteristic - not connected"); - return; - } else if (this->ble_char_handle_ == 0) { - ESP_LOGW(TAG, "Cannot write to BLE characteristic - characteristic not found"); - return; - } - esp_gatt_write_type_t write_type; - if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) { - write_type = ESP_GATT_WRITE_TYPE_RSP; - ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP"); - } else if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) { - write_type = ESP_GATT_WRITE_TYPE_NO_RSP; - ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP"); - } else { - ESP_LOGE(TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str()); - return; - } - ESP_LOGVV(TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str()); - esp_err_t err = - esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->ble_char_handle_, - value.size(), const_cast(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Error writing to characteristic: %s!", esp_err_to_name(err)); - } -} - -void BLEWriterClientNode::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *param) { - switch (event) { - case ESP_GATTC_REG_EVT: - break; - case ESP_GATTC_OPEN_EVT: - this->node_state = espbt::ClientState::ESTABLISHED; - ESP_LOGD(TAG, "Connection established with %s", ble_client_->address_str().c_str()); - break; - case ESP_GATTC_SEARCH_CMPL_EVT: { - auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); - if (chr == nullptr) { - ESP_LOGW("ble_write_action", "Characteristic %s was not found in service %s", - this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str()); - break; - } - this->ble_char_handle_ = chr->handle; - this->char_props_ = chr->properties; - this->node_state = espbt::ClientState::ESTABLISHED; - ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), - ble_client_->address_str().c_str()); - break; - } - case ESP_GATTC_DISCONNECT_EVT: - this->node_state = espbt::ClientState::IDLE; - this->ble_char_handle_ = 0; - ESP_LOGD(TAG, "Disconnected from %s", ble_client_->address_str().c_str()); - break; - default: - break; - } -} +const char *const Automation::TAG = "ble_client.automation"; } // namespace ble_client } // namespace esphome diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 423f74b85a..a5c661e2f5 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -7,9 +7,19 @@ #include "esphome/core/automation.h" #include "esphome/components/ble_client/ble_client.h" +#include "esphome/core/log.h" namespace esphome { namespace ble_client { + +// placeholder class for static TAG . +class Automation { + public: + // could be made inline with C++17 + static const char *const TAG; +}; + +// implement on_connect automation. class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode { public: explicit BLEClientConnectTrigger(BLEClient *parent) { parent->register_ble_node(this); } @@ -23,17 +33,28 @@ class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode { } }; +// on_disconnect automation class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode { public: explicit BLEClientDisconnectTrigger(BLEClient *parent) { parent->register_ble_node(this); } void loop() override {} void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override { - if (event == ESP_GATTC_DISCONNECT_EVT && - memcmp(param->disconnect.remote_bda, this->parent_->get_remote_bda(), 6) == 0) - this->trigger(); - if (event == ESP_GATTC_SEARCH_CMPL_EVT) - this->node_state = espbt::ClientState::ESTABLISHED; + // test for CLOSE and not DISCONNECT - DISCONNECT can occur even if no virtual connection (OPEN event) occurred. + // So this will not trigger unless a complete open has previously succeeded. + switch (event) { + case ESP_GATTC_SEARCH_CMPL_EVT: { + this->node_state = espbt::ClientState::ESTABLISHED; + break; + } + case ESP_GATTC_CLOSE_EVT: { + this->trigger(); + break; + } + default: { + break; + } + } } }; @@ -42,10 +63,8 @@ class BLEClientPasskeyRequestTrigger : public Trigger<>, public BLEClientNode { explicit BLEClientPasskeyRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } void loop() override {} void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { - if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT && - memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { + if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) this->trigger(); - } } }; @@ -54,10 +73,8 @@ class BLEClientPasskeyNotificationTrigger : public Trigger, public BLE explicit BLEClientPasskeyNotificationTrigger(BLEClient *parent) { parent->register_ble_node(this); } void loop() override {} void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { - if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT && - memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { - uint32_t passkey = param->ble_security.key_notif.passkey; - this->trigger(passkey); + if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) { + this->trigger(param->ble_security.key_notif.passkey); } } }; @@ -67,24 +84,20 @@ class BLEClientNumericComparisonRequestTrigger : public Trigger, publi explicit BLEClientNumericComparisonRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } void loop() override {} void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { - if (event == ESP_GAP_BLE_NC_REQ_EVT && - memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { - uint32_t passkey = param->ble_security.key_notif.passkey; - this->trigger(passkey); + if (event == ESP_GAP_BLE_NC_REQ_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) { + this->trigger(param->ble_security.key_notif.passkey); } } }; -class BLEWriterClientNode : public BLEClientNode { +// implement the ble_client.ble_write action. +template class BLEClientWriteAction : public Action, public BLEClientNode { public: - BLEWriterClientNode(BLEClient *ble_client) { + BLEClientWriteAction(BLEClient *ble_client) { ble_client->register_ble_node(this); ble_client_ = ble_client; } - // Attempts to write the contents of value to char_uuid_. - void write(const std::vector &value); - void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } @@ -93,29 +106,6 @@ class BLEWriterClientNode : public BLEClientNode { void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } - void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *param) override; - - private: - BLEClient *ble_client_; - int ble_char_handle_ = 0; - esp_gatt_char_prop_t char_props_; - espbt::ESPBTUUID service_uuid_; - espbt::ESPBTUUID char_uuid_; -}; - -template class BLEClientWriteAction : public Action, public BLEWriterClientNode { - public: - BLEClientWriteAction(BLEClient *ble_client) : BLEWriterClientNode(ble_client) {} - - void play(Ts... x) override { - if (has_simple_value_) { - return write(this->value_simple_); - } else { - return write(this->value_template_(x...)); - } - } - void set_value_template(std::function(Ts...)> func) { this->value_template_ = std::move(func); has_simple_value_ = false; @@ -126,10 +116,94 @@ template class BLEClientWriteAction : public Action, publ has_simple_value_ = true; } + void play(Ts... x) override {} + + void play_complex(Ts... x) override { + this->num_running_++; + this->var_ = std::make_tuple(x...); + auto value = this->has_simple_value_ ? this->value_simple_ : this->value_template_(x...); + // on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work. + if (!write(value)) + this->play_next_(x...); + } + + /** + * Note about logging: the esph_log_X macros are used here because the CI checks complain about use of the ESP LOG + * macros in header files (Can't even write it in a comment!) + * Not sure why, because they seem to work just fine. + * The problem is that the implementation of a templated class can't be placed in a .cpp file when using C++ less than + * 17, so the methods have to be here. The esph_log_X macros are equivalent in function, but don't trigger the CI + * errors. + */ + // initiate the write. Return true if all went well, will be followed by a WRITE_CHAR event. + bool write(const std::vector &value) { + if (this->node_state != espbt::ClientState::ESTABLISHED) { + esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected"); + return false; + } + esph_log_vv(Automation::TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str()); + esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), + this->char_handle_, value.size(), const_cast(value.data()), + this->write_type_, ESP_GATT_AUTH_REQ_NONE); + if (err != ESP_OK) { + esph_log_e(Automation::TAG, "Error writing to characteristic: %s!", esp_err_to_name(err)); + return false; + } + return true; + } + + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override { + switch (event) { + case ESP_GATTC_WRITE_CHAR_EVT: + // upstream code checked the MAC address, verify the characteristic. + if (param->write.handle == this->char_handle_) + this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); }); + break; + case ESP_GATTC_DISCONNECT_EVT: + if (this->num_running_ != 0) + this->stop_complex(); + break; + case ESP_GATTC_SEARCH_CMPL_EVT: { + auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); + if (chr == nullptr) { + esph_log_w("ble_write_action", "Characteristic %s was not found in service %s", + this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str()); + break; + } + this->char_handle_ = chr->handle; + this->char_props_ = chr->properties; + if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) { + this->write_type_ = ESP_GATT_WRITE_TYPE_RSP; + esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP"); + } else if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) { + this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP; + esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP"); + } else { + esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str()); + break; + } + this->node_state = espbt::ClientState::ESTABLISHED; + esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), + ble_client_->address_str().c_str()); + break; + } + default: + break; + } + } + private: + BLEClient *ble_client_; bool has_simple_value_ = true; std::vector value_simple_; std::function(Ts...)> value_template_{}; + espbt::ESPBTUUID service_uuid_; + espbt::ESPBTUUID char_uuid_; + std::tuple var_{}; + uint16_t char_handle_{}; + esp_gatt_char_prop_t char_props_{}; + esp_gatt_write_type_t write_type_{}; }; template class BLEClientPasskeyReplyAction : public Action { @@ -212,6 +286,92 @@ template class BLEClientRemoveBondAction : public Action BLEClient *parent_{nullptr}; }; +template class BLEClientConnectAction : public Action, public BLEClientNode { + public: + BLEClientConnectAction(BLEClient *ble_client) { + ble_client->register_ble_node(this); + ble_client_ = ble_client; + } + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override { + if (this->num_running_ == 0) + return; + switch (event) { + case ESP_GATTC_SEARCH_CMPL_EVT: + this->node_state = espbt::ClientState::ESTABLISHED; + this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); }); + break; + // if the connection is closed, terminate the automation chain. + case ESP_GATTC_DISCONNECT_EVT: + this->stop_complex(); + break; + default: + break; + } + } + + // not used since we override play_complex_ + void play(Ts... x) override {} + + void play_complex(Ts... x) override { + // it makes no sense to have multiple instances of this running at the same time. + // this would occur only if the same automation was re-triggered while still + // running. So just cancel the second chain if this is detected. + if (this->num_running_ != 0) { + this->stop_complex(); + return; + } + this->num_running_++; + if (this->node_state == espbt::ClientState::ESTABLISHED) { + this->play_next_(x...); + } else { + this->var_ = std::make_tuple(x...); + this->ble_client_->connect(); + } + } + + private: + BLEClient *ble_client_; + std::tuple var_{}; +}; + +template class BLEClientDisconnectAction : public Action, public BLEClientNode { + public: + BLEClientDisconnectAction(BLEClient *ble_client) { + ble_client->register_ble_node(this); + ble_client_ = ble_client; + } + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override { + if (this->num_running_ == 0) + return; + switch (event) { + case ESP_GATTC_CLOSE_EVT: + case ESP_GATTC_DISCONNECT_EVT: + this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); }); + break; + default: + break; + } + } + + // not used since we override play_complex_ + void play(Ts... x) override {} + + void play_complex(Ts... x) override { + this->num_running_++; + if (this->node_state == espbt::ClientState::IDLE) { + this->play_next_(x...); + } else { + this->var_ = std::make_tuple(x...); + this->ble_client_->disconnect(); + } + } + + private: + BLEClient *ble_client_; + std::tuple var_{}; +}; } // namespace ble_client } // namespace esphome diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index f3a9f01a1a..19cf2bc1f3 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -26,6 +26,7 @@ void BLEClient::loop() { void BLEClient::dump_config() { ESP_LOGCONFIG(TAG, "BLE Client:"); ESP_LOGCONFIG(TAG, " Address: %s", this->address_str().c_str()); + ESP_LOGCONFIG(TAG, " Auto-Connect: %s", TRUEFALSE(this->auto_connect_)); } bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { @@ -37,31 +38,24 @@ bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { void BLEClient::set_enabled(bool enabled) { if (enabled == this->enabled) return; - if (!enabled && this->state() != espbt::ClientState::IDLE) { - ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str()); - auto ret = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); - if (ret) { - ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret); - } - } this->enabled = enabled; + if (!enabled) { + ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str()); + this->disconnect(); + } } bool BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if, esp_ble_gattc_cb_param_t *param) { - bool all_established = this->all_nodes_established_(); - if (!BLEClientBase::gattc_event_handler(event, esp_gattc_if, param)) return false; for (auto *node : this->nodes_) node->gattc_event_handler(event, esp_gattc_if, param); - // Delete characteristics after clients have used them to save RAM. - if (!all_established && this->all_nodes_established_()) { - for (auto &svc : this->services_) - delete svc; // NOLINT(cppcoreguidelines-owning-memory) - this->services_.clear(); + if (!this->services_.empty() && this->all_nodes_established_()) { + this->release_services(); + ESP_LOGD(TAG, "All clients established, services released"); } return true; } diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp index 6709803936..3f05bc4b84 100644 --- a/esphome/components/ble_client/output/ble_binary_output.cpp +++ b/esphome/components/ble_client/output/ble_binary_output.cpp @@ -19,26 +19,36 @@ void BLEBinaryOutput::dump_config() { void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { switch (event) { - case ESP_GATTC_OPEN_EVT: - this->client_state_ = espbt::ClientState::ESTABLISHED; - ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str()); - break; - case ESP_GATTC_DISCONNECT_EVT: - ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str()); - this->client_state_ = espbt::ClientState::IDLE; - break; - case ESP_GATTC_WRITE_CHAR_EVT: { - if (param->write.status == 0) { - break; - } - + case ESP_GATTC_SEARCH_CMPL_EVT: { auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { - ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str()); + ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_string().c_str(), + this->service_uuid_.to_string().c_str()); break; } - if (param->write.handle == chr->handle) { - ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status); + this->char_handle_ = chr->handle; + this->char_props_ = chr->properties; + if (this->require_response_ && this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) { + this->write_type_ = ESP_GATT_WRITE_TYPE_RSP; + ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP"); + } else if (!this->require_response_ && this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) { + this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP; + ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP"); + } else { + ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_string().c_str(), + this->require_response_ ? "" : "out"); + break; + } + this->node_state = espbt::ClientState::ESTABLISHED; + ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), + this->parent()->address_str().c_str()); + this->node_state = espbt::ClientState::ESTABLISHED; + break; + } + case ESP_GATTC_WRITE_CHAR_EVT: { + if (param->write.handle == this->char_handle_) { + if (param->write.status != 0) + ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status); } break; } @@ -48,26 +58,18 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i } void BLEBinaryOutput::write_state(bool state) { - if (this->client_state_ != espbt::ClientState::ESTABLISHED) { + if (this->node_state != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.", this->char_uuid_.to_string().c_str()); return; } - - auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); - if (chr == nullptr) { - ESP_LOGW(TAG, "[%s] Characteristic not found. State update can not be written.", - this->char_uuid_.to_string().c_str()); - return; - } - uint8_t state_as_uint = (uint8_t) state; ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint); - if (this->require_response_) { - chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_RSP); - } else { - chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_NO_RSP); - } + esp_err_t err = + esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, + sizeof(state_as_uint), &state_as_uint, this->write_type_, ESP_GATT_AUTH_REQ_NONE); + if (err != ESP_GATT_OK) + ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_string().c_str(), err); } } // namespace ble_client diff --git a/esphome/components/ble_client/output/ble_binary_output.h b/esphome/components/ble_client/output/ble_binary_output.h index 83eabcf5f2..0a1e186b26 100644 --- a/esphome/components/ble_client/output/ble_binary_output.h +++ b/esphome/components/ble_client/output/ble_binary_output.h @@ -32,7 +32,9 @@ class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, publi bool require_response_; espbt::ESPBTUUID service_uuid_; espbt::ESPBTUUID char_uuid_; - espbt::ClientState client_state_; + uint16_t char_handle_{}; + esp_gatt_char_prop_t char_props_{}; + esp_gatt_write_type_t write_type_{}; }; } // namespace ble_client diff --git a/esphome/components/ble_client/sensor/automation.h b/esphome/components/ble_client/sensor/automation.h index d830165d30..56ab7ba4c9 100644 --- a/esphome/components/ble_client/sensor/automation.h +++ b/esphome/components/ble_client/sensor/automation.h @@ -14,15 +14,17 @@ class BLESensorNotifyTrigger : public Trigger, public BLESensor { void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override { switch (event) { - case ESP_GATTC_SEARCH_CMPL_EVT: { - this->sensor_->node_state = espbt::ClientState::ESTABLISHED; + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.handle == this->sensor_->handle) + this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len)); break; } - case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() || - param->notify.handle != this->sensor_->handle) - break; - this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len)); + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + // confirms notifications are being listened for. While enabling of notifications may still be in + // progress by the parent, we assume it will happen. + if (param->reg_for_notify.status == ESP_GATT_OK && param->reg_for_notify.handle == this->sensor_->handle) + this->node_state = espbt::ClientState::ESTABLISHED; + break; } default: break; diff --git a/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp index a36e191e32..81d244ce6d 100644 --- a/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp @@ -22,26 +22,19 @@ void BLEClientRSSISensor::dump_config() { void BLEClientRSSISensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { switch (event) { - case ESP_GATTC_OPEN_EVT: { - if (param->open.status == ESP_GATT_OK) { - ESP_LOGI(TAG, "[%s] Connected successfully!", this->get_name().c_str()); - break; - } - break; - } - case ESP_GATTC_DISCONNECT_EVT: { - ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str()); + case ESP_GATTC_CLOSE_EVT: { this->status_set_warning(); this->publish_state(NAN); break; } - case ESP_GATTC_SEARCH_CMPL_EVT: + case ESP_GATTC_SEARCH_CMPL_EVT: { this->node_state = espbt::ClientState::ESTABLISHED; if (this->should_update_) { this->should_update_ = false; this->get_rssi_(); } break; + } default: break; } diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index a05efad60b..43f61f5304 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -33,7 +33,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga } break; } - case ESP_GATTC_DISCONNECT_EVT: { + case ESP_GATTC_CLOSE_EVT: { ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str()); this->status_set_warning(); this->publish_state(NAN); @@ -74,8 +74,6 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->get_conn_id()) - break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); break; @@ -87,15 +85,23 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle) - break; - ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), + ESP_LOGD(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); + if (param->notify.handle != this->handle) + break; this->publish_state(this->parse_data_(param->notify.value, param->notify.value_len)); break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - this->node_state = espbt::ClientState::ESTABLISHED; + if (param->reg_for_notify.handle == this->handle) { + if (param->reg_for_notify.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error registering for notifications at handle %d, status=%d", param->reg_for_notify.handle, + param->reg_for_notify.status); + break; + } + this->node_state = espbt::ClientState::ESTABLISHED; + ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_string().c_str()); + } break; } default: diff --git a/esphome/components/ble_client/switch/ble_switch.cpp b/esphome/components/ble_client/switch/ble_switch.cpp index 6de5252404..9d92b1b2b5 100644 --- a/esphome/components/ble_client/switch/ble_switch.cpp +++ b/esphome/components/ble_client/switch/ble_switch.cpp @@ -17,14 +17,11 @@ void BLEClientSwitch::write_state(bool state) { void BLEClientSwitch::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { switch (event) { - case ESP_GATTC_REG_EVT: + case ESP_GATTC_CLOSE_EVT: this->publish_state(this->parent_->enabled); break; - case ESP_GATTC_OPEN_EVT: + case ESP_GATTC_SEARCH_CMPL_EVT: this->node_state = espbt::ClientState::ESTABLISHED; - break; - case ESP_GATTC_DISCONNECT_EVT: - this->node_state = espbt::ClientState::IDLE; this->publish_state(this->parent_->enabled); break; default: diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index 1a304593c7..33938ee7b7 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -36,8 +36,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } break; } - case ESP_GATTC_DISCONNECT_EVT: { - ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str()); + case ESP_GATTC_CLOSE_EVT: { this->status_set_warning(); this->publish_state(EMPTY); break; @@ -77,20 +76,18 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->get_conn_id()) - break; - if (param->read.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); - break; - } if (param->read.handle == this->handle) { + if (param->read.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); + break; + } this->status_clear_warning(); this->publish_state(this->parse_data(param->read.value, param->read.value_len)); } break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle) + if (param->notify.handle != this->handle) break; ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); @@ -98,7 +95,8 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - this->node_state = espbt::ClientState::ESTABLISHED; + if (param->reg_for_notify.status == ESP_GATT_OK && param->reg_for_notify.handle == this->handle) + this->node_state = espbt::ClientState::ESTABLISHED; break; } default: diff --git a/esphome/components/ble_presence/binary_sensor.py b/esphome/components/ble_presence/binary_sensor.py index 81878391bb..9c24a91a05 100644 --- a/esphome/components/ble_presence/binary_sensor.py +++ b/esphome/components/ble_presence/binary_sensor.py @@ -8,8 +8,11 @@ from esphome.const import ( CONF_IBEACON_MINOR, CONF_IBEACON_UUID, CONF_MIN_RSSI, + CONF_TIMEOUT, ) +CONF_IRK = "irk" + DEPENDENCIES = ["esp32_ble_tracker"] ble_presence_ns = cg.esphome_ns.namespace("ble_presence") @@ -34,10 +37,12 @@ CONFIG_SCHEMA = cv.All( .extend( { cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_IRK): cv.uuid, cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, cv.Optional(CONF_IBEACON_UUID): cv.uuid, + cv.Optional(CONF_TIMEOUT, default="5min"): cv.positive_time_period, cv.Optional(CONF_MIN_RSSI): cv.All( cv.decibel, cv.int_range(min=-100, max=-30) ), @@ -45,7 +50,9 @@ CONFIG_SCHEMA = cv.All( ) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) .extend(cv.COMPONENT_SCHEMA), - cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_IBEACON_UUID), + cv.has_exactly_one_key( + CONF_MAC_ADDRESS, CONF_IRK, CONF_SERVICE_UUID, CONF_IBEACON_UUID + ), _validate, ) @@ -55,12 +62,17 @@ async def to_code(config): await cg.register_component(var, config) await esp32_ble_tracker.register_ble_device(var, config) + cg.add(var.set_timeout(config[CONF_TIMEOUT].total_milliseconds)) if min_rssi := config.get(CONF_MIN_RSSI): cg.add(var.set_minimum_rssi(min_rssi)) if mac_address := config.get(CONF_MAC_ADDRESS): cg.add(var.set_address(mac_address.as_hex)) + if irk := config.get(CONF_IRK): + irk = esp32_ble_tracker.as_hex_array(str(irk)) + cg.add(var.set_irk(irk)) + if service_uuid := config.get(CONF_SERVICE_UUID): if len(service_uuid) == len(esp32_ble_tracker.bt_uuid16_format): cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(service_uuid))) diff --git a/esphome/components/ble_presence/ble_presence_device.h b/esphome/components/ble_presence/ble_presence_device.h index 1be9adeb30..0d86f6a40d 100644 --- a/esphome/components/ble_presence/ble_presence_device.h +++ b/esphome/components/ble_presence/ble_presence_device.h @@ -6,6 +6,16 @@ #ifdef USE_ESP32 +#ifdef USE_ARDUINO +#include "mbedtls/aes.h" +#include "mbedtls/base64.h" +#endif + +#ifdef USE_ESP_IDF +#define MBEDTLS_AES_ALT +#include +#endif + namespace esphome { namespace ble_presence { @@ -17,6 +27,10 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, this->match_by_ = MATCH_BY_MAC_ADDRESS; this->address_ = address; } + void set_irk(uint8_t *irk) { + this->match_by_ = MATCH_BY_IRK; + this->irk_ = irk; + } void set_service_uuid16(uint16_t uuid) { this->match_by_ = MATCH_BY_SERVICE_UUID; this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid); @@ -45,11 +59,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, this->check_minimum_rssi_ = true; this->minimum_rssi_ = rssi; } - void on_scan_end() override { - if (!this->found_) - this->publish_state(false); - this->found_ = false; - } + void set_timeout(uint32_t timeout) { this->timeout_ = timeout; } bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { if (this->check_minimum_rssi_ && this->minimum_rssi_ > device.get_rssi()) { return false; @@ -57,6 +67,12 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, switch (this->match_by_) { case MATCH_BY_MAC_ADDRESS: if (device.address_uint64() == this->address_) { + this->set_found_(true); + return true; + } + break; + case MATCH_BY_IRK: + if (resolve_irk_(device.address_uint64(), this->irk_)) { this->publish_state(true); this->found_ = true; return true; @@ -65,8 +81,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, case MATCH_BY_SERVICE_UUID: for (auto uuid : device.get_service_uuids()) { if (this->uuid_ == uuid) { - this->publish_state(true); - this->found_ = true; + this->set_found_(true); return true; } } @@ -90,20 +105,31 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, return false; } - this->publish_state(true); - this->found_ = true; + this->set_found_(true); return true; } return false; } + + void loop() override { + if (this->found_ && this->last_seen_ + this->timeout_ < millis()) + this->set_found_(false); + } void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } protected: - enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID }; + void set_found_(bool state) { + this->found_ = state; + if (state) + this->last_seen_ = millis(); + this->publish_state(state); + } + enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_IRK, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID }; MatchType match_by_; uint64_t address_; + uint8_t *irk_; esp32_ble_tracker::ESPBTUUID uuid_; @@ -117,7 +143,46 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, bool check_ibeacon_minor_{false}; bool check_minimum_rssi_{false}; + bool resolve_irk_(uint64_t addr64, const uint8_t *irk) { + uint8_t ecb_key[16]; + uint8_t ecb_plaintext[16]; + uint8_t ecb_ciphertext[16]; + + memcpy(&ecb_key, irk, 16); + memset(&ecb_plaintext, 0, 16); + + ecb_plaintext[13] = (addr64 >> 40) & 0xff; + ecb_plaintext[14] = (addr64 >> 32) & 0xff; + ecb_plaintext[15] = (addr64 >> 24) & 0xff; + + mbedtls_aes_context ctx = {0, 0, {0}}; + mbedtls_aes_init(&ctx); + + if (mbedtls_aes_setkey_enc(&ctx, ecb_key, 128) != 0) { + mbedtls_aes_free(&ctx); + return false; + } + + if (mbedtls_aes_crypt_ecb(&ctx, +#ifdef USE_ARDUINO + MBEDTLS_AES_ENCRYPT, +#elif defined(USE_ESP_IDF) + ESP_AES_ENCRYPT, +#endif + ecb_plaintext, ecb_ciphertext) != 0) { + mbedtls_aes_free(&ctx); + return false; + } + + mbedtls_aes_free(&ctx); + + return ecb_ciphertext[15] == (addr64 & 0xff) && ecb_ciphertext[14] == ((addr64 >> 8) & 0xff) && + ecb_ciphertext[13] == ((addr64 >> 16) & 0xff); + } + bool found_{false}; + uint32_t last_seen_{}; + uint32_t timeout_{}; }; } // namespace ble_presence diff --git a/esphome/components/bme280/sensor.py b/esphome/components/bme280/sensor.py deleted file mode 100644 index 35744a436d..0000000000 --- a/esphome/components/bme280/sensor.py +++ /dev/null @@ -1,116 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import i2c, sensor -from esphome.const import ( - CONF_HUMIDITY, - CONF_ID, - CONF_IIR_FILTER, - CONF_OVERSAMPLING, - CONF_PRESSURE, - CONF_TEMPERATURE, - DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_PRESSURE, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, - UNIT_CELSIUS, - UNIT_HECTOPASCAL, - UNIT_PERCENT, -) - -DEPENDENCIES = ["i2c"] - -bme280_ns = cg.esphome_ns.namespace("bme280") -BME280Oversampling = bme280_ns.enum("BME280Oversampling") -OVERSAMPLING_OPTIONS = { - "NONE": BME280Oversampling.BME280_OVERSAMPLING_NONE, - "1X": BME280Oversampling.BME280_OVERSAMPLING_1X, - "2X": BME280Oversampling.BME280_OVERSAMPLING_2X, - "4X": BME280Oversampling.BME280_OVERSAMPLING_4X, - "8X": BME280Oversampling.BME280_OVERSAMPLING_8X, - "16X": BME280Oversampling.BME280_OVERSAMPLING_16X, -} - -BME280IIRFilter = bme280_ns.enum("BME280IIRFilter") -IIR_FILTER_OPTIONS = { - "OFF": BME280IIRFilter.BME280_IIR_FILTER_OFF, - "2X": BME280IIRFilter.BME280_IIR_FILTER_2X, - "4X": BME280IIRFilter.BME280_IIR_FILTER_4X, - "8X": BME280IIRFilter.BME280_IIR_FILTER_8X, - "16X": BME280IIRFilter.BME280_IIR_FILTER_16X, -} - -BME280Component = bme280_ns.class_( - "BME280Component", cg.PollingComponent, i2c.I2CDevice -) - -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(BME280Component), - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - accuracy_decimals=1, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( - OVERSAMPLING_OPTIONS, upper=True - ), - } - ), - cv.Optional(CONF_PRESSURE): sensor.sensor_schema( - unit_of_measurement=UNIT_HECTOPASCAL, - accuracy_decimals=1, - device_class=DEVICE_CLASS_PRESSURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( - OVERSAMPLING_OPTIONS, upper=True - ), - } - ), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - unit_of_measurement=UNIT_PERCENT, - accuracy_decimals=1, - device_class=DEVICE_CLASS_HUMIDITY, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( - OVERSAMPLING_OPTIONS, upper=True - ), - } - ), - cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( - IIR_FILTER_OPTIONS, upper=True - ), - } - ) - .extend(cv.polling_component_schema("60s")) - .extend(i2c.i2c_device_schema(0x77)) -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await i2c.register_i2c_device(var, config) - - if temperature_config := config.get(CONF_TEMPERATURE): - sens = await sensor.new_sensor(temperature_config) - cg.add(var.set_temperature_sensor(sens)) - cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING])) - - if pressure_config := config.get(CONF_PRESSURE): - sens = await sensor.new_sensor(pressure_config) - cg.add(var.set_pressure_sensor(sens)) - cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING])) - - if humidity_config := config.get(CONF_HUMIDITY): - sens = await sensor.new_sensor(humidity_config) - cg.add(var.set_humidity_sensor(sens)) - cg.add(var.set_humidity_oversampling(humidity_config[CONF_OVERSAMPLING])) - - cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) diff --git a/esphome/components/bme280_base/__init__.py b/esphome/components/bme280_base/__init__.py new file mode 100644 index 0000000000..f70ffa9520 --- /dev/null +++ b/esphome/components/bme280_base/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@esphome/core"] diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280_base/bme280_base.cpp similarity index 92% rename from esphome/components/bme280/bme280.cpp rename to esphome/components/bme280_base/bme280_base.cpp index 786fc01d28..76e20836c7 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280_base/bme280_base.cpp @@ -1,9 +1,14 @@ -#include "bme280.h" +#include +#include + +#include "bme280_base.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include +#include namespace esphome { -namespace bme280 { +namespace bme280_base { static const char *const TAG = "bme280.sensor"; @@ -46,7 +51,24 @@ static const uint8_t BME280_STATUS_IM_UPDATE = 0b01; inline uint16_t combine_bytes(uint8_t msb, uint8_t lsb) { return ((msb & 0xFF) << 8) | (lsb & 0xFF); } -static const char *oversampling_to_str(BME280Oversampling oversampling) { +const char *iir_filter_to_str(BME280IIRFilter filter) { // NOLINT + switch (filter) { + case BME280_IIR_FILTER_OFF: + return "OFF"; + case BME280_IIR_FILTER_2X: + return "2x"; + case BME280_IIR_FILTER_4X: + return "4x"; + case BME280_IIR_FILTER_8X: + return "8x"; + case BME280_IIR_FILTER_16X: + return "16x"; + default: + return "UNKNOWN"; + } +} + +const char *oversampling_to_str(BME280Oversampling oversampling) { // NOLINT switch (oversampling) { case BME280_OVERSAMPLING_NONE: return "None"; @@ -65,23 +87,6 @@ static const char *oversampling_to_str(BME280Oversampling oversampling) { } } -static const char *iir_filter_to_str(BME280IIRFilter filter) { - switch (filter) { - case BME280_IIR_FILTER_OFF: - return "OFF"; - case BME280_IIR_FILTER_2X: - return "2x"; - case BME280_IIR_FILTER_4X: - return "4x"; - case BME280_IIR_FILTER_8X: - return "8x"; - case BME280_IIR_FILTER_16X: - return "16x"; - default: - return "UNKNOWN"; - } -} - void BME280Component::setup() { ESP_LOGCONFIG(TAG, "Setting up BME280..."); uint8_t chip_id = 0; @@ -112,7 +117,7 @@ void BME280Component::setup() { // Wait until the NVM data has finished loading. uint8_t status; uint8_t retry = 5; - do { + do { // NOLINT delay(2); if (!this->read_byte(BME280_REGISTER_STATUS, &status)) { ESP_LOGW(TAG, "Error reading status register."); @@ -175,7 +180,6 @@ void BME280Component::setup() { } void BME280Component::dump_config() { ESP_LOGCONFIG(TAG, "BME280:"); - LOG_I2C_DEVICE(this); switch (this->error_code_) { case COMMUNICATION_FAILED: ESP_LOGE(TAG, "Communication with BME280 failed!"); @@ -226,14 +230,14 @@ void BME280Component::update() { return; } int32_t t_fine = 0; - float temperature = this->read_temperature_(data, &t_fine); + float const temperature = this->read_temperature_(data, &t_fine); if (std::isnan(temperature)) { ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); this->status_set_warning(); return; } - float pressure = this->read_pressure_(data, t_fine); - float humidity = this->read_humidity_(data, t_fine); + float const pressure = this->read_pressure_(data, t_fine); + float const humidity = this->read_humidity_(data, t_fine); ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); if (this->temperature_sensor_ != nullptr) @@ -257,12 +261,12 @@ float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) { const int32_t t2 = this->calibration_.t2; const int32_t t3 = this->calibration_.t3; - int32_t var1 = (((adc >> 3) - (t1 << 1)) * t2) >> 11; - int32_t var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14; + int32_t const var1 = (((adc >> 3) - (t1 << 1)) * t2) >> 11; + int32_t const var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14; *t_fine = var1 + var2; - float temperature = (*t_fine * 5 + 128) >> 8; - return temperature / 100.0f; + float const temperature = (*t_fine * 5 + 128); + return temperature / 25600.0f; } float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) { @@ -303,11 +307,11 @@ float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) { } float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) { - uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF); + uint16_t const raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF); if (raw_adc == 0x8000) return NAN; - int32_t adc = raw_adc; + int32_t const adc = raw_adc; const int32_t h1 = this->calibration_.h1; const int32_t h2 = this->calibration_.h2; @@ -325,7 +329,7 @@ float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) { v_x1_u32r = v_x1_u32r < 0 ? 0 : v_x1_u32r; v_x1_u32r = v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r; - float h = v_x1_u32r >> 12; + float const h = v_x1_u32r >> 12; return h / 1024.0f; } @@ -351,5 +355,5 @@ uint16_t BME280Component::read_u16_le_(uint8_t a_register) { } int16_t BME280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); } -} // namespace bme280 +} // namespace bme280_base } // namespace esphome diff --git a/esphome/components/bme280/bme280.h b/esphome/components/bme280_base/bme280_base.h similarity index 90% rename from esphome/components/bme280/bme280.h rename to esphome/components/bme280_base/bme280_base.h index 50d398c40f..0f55ad0101 100644 --- a/esphome/components/bme280/bme280.h +++ b/esphome/components/bme280_base/bme280_base.h @@ -2,10 +2,9 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" namespace esphome { -namespace bme280 { +namespace bme280_base { /// Internal struct storing the calibration values of an BME280. struct BME280CalibrationData { @@ -57,8 +56,8 @@ enum BME280IIRFilter { BME280_IIR_FILTER_16X = 0b100, }; -/// This class implements support for the BME280 Temperature+Pressure+Humidity i2c sensor. -class BME280Component : public PollingComponent, public i2c::I2CDevice { +/// This class implements support for the BME280 Temperature+Pressure+Humidity sensor. +class BME280Component : public PollingComponent { public: void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } @@ -91,6 +90,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice { uint16_t read_u16_le_(uint8_t a_register); int16_t read_s16_le_(uint8_t a_register); + virtual bool read_byte(uint8_t a_register, uint8_t *data) = 0; + virtual bool write_byte(uint8_t a_register, uint8_t data) = 0; + virtual bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0; + virtual bool read_byte_16(uint8_t a_register, uint16_t *data) = 0; + BME280CalibrationData calibration_; BME280Oversampling temperature_oversampling_{BME280_OVERSAMPLING_16X}; BME280Oversampling pressure_oversampling_{BME280_OVERSAMPLING_16X}; @@ -106,5 +110,5 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice { } error_code_{NONE}; }; -} // namespace bme280 +} // namespace bme280_base } // namespace esphome diff --git a/esphome/components/bme280_base/sensor.py b/esphome/components/bme280_base/sensor.py new file mode 100644 index 0000000000..3a745ed348 --- /dev/null +++ b/esphome/components/bme280_base/sensor.py @@ -0,0 +1,106 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_IIR_FILTER, + CONF_OVERSAMPLING, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, + UNIT_PERCENT, +) + +bme280_ns = cg.esphome_ns.namespace("bme280_base") +BME280Oversampling = bme280_ns.enum("BME280Oversampling") +OVERSAMPLING_OPTIONS = { + "NONE": BME280Oversampling.BME280_OVERSAMPLING_NONE, + "1X": BME280Oversampling.BME280_OVERSAMPLING_1X, + "2X": BME280Oversampling.BME280_OVERSAMPLING_2X, + "4X": BME280Oversampling.BME280_OVERSAMPLING_4X, + "8X": BME280Oversampling.BME280_OVERSAMPLING_8X, + "16X": BME280Oversampling.BME280_OVERSAMPLING_16X, +} + +BME280IIRFilter = bme280_ns.enum("BME280IIRFilter") +IIR_FILTER_OPTIONS = { + "OFF": BME280IIRFilter.BME280_IIR_FILTER_OFF, + "2X": BME280IIRFilter.BME280_IIR_FILTER_2X, + "4X": BME280IIRFilter.BME280_IIR_FILTER_4X, + "8X": BME280IIRFilter.BME280_IIR_FILTER_8X, + "16X": BME280IIRFilter.BME280_IIR_FILTER_16X, +} + +CONFIG_SCHEMA_BASE = cv.Schema( + { + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( + IIR_FILTER_OPTIONS, upper=True + ), + } +).extend(cv.polling_component_schema("60s")) + + +async def to_code(config, func=None): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + if func is not None: + await func(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING])) + + if pressure_config := config.get(CONF_PRESSURE): + sens = await sensor.new_sensor(pressure_config) + cg.add(var.set_pressure_sensor(sens)) + cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING])) + + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity_sensor(sens)) + cg.add(var.set_humidity_oversampling(humidity_config[CONF_OVERSAMPLING])) + + cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) diff --git a/esphome/components/bme280/__init__.py b/esphome/components/bme280_i2c/__init__.py similarity index 100% rename from esphome/components/bme280/__init__.py rename to esphome/components/bme280_i2c/__init__.py diff --git a/esphome/components/bme280_i2c/bme280_i2c.cpp b/esphome/components/bme280_i2c/bme280_i2c.cpp new file mode 100644 index 0000000000..e29675b5b7 --- /dev/null +++ b/esphome/components/bme280_i2c/bme280_i2c.cpp @@ -0,0 +1,30 @@ +#include +#include + +#include "bme280_i2c.h" +#include "esphome/components/i2c/i2c.h" +#include "../bme280_base/bme280_base.h" + +namespace esphome { +namespace bme280_i2c { + +bool BME280I2CComponent::read_byte(uint8_t a_register, uint8_t *data) { + return I2CDevice::read_byte(a_register, data); +}; +bool BME280I2CComponent::write_byte(uint8_t a_register, uint8_t data) { + return I2CDevice::write_byte(a_register, data); +}; +bool BME280I2CComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) { + return I2CDevice::read_bytes(a_register, data, len); +}; +bool BME280I2CComponent::read_byte_16(uint8_t a_register, uint16_t *data) { + return I2CDevice::read_byte_16(a_register, data); +}; + +void BME280I2CComponent::dump_config() { + LOG_I2C_DEVICE(this); + BME280Component::dump_config(); +} + +} // namespace bme280_i2c +} // namespace esphome diff --git a/esphome/components/bme280_i2c/bme280_i2c.h b/esphome/components/bme280_i2c/bme280_i2c.h new file mode 100644 index 0000000000..c5e2f7e342 --- /dev/null +++ b/esphome/components/bme280_i2c/bme280_i2c.h @@ -0,0 +1,20 @@ +#pragma once + +#include "esphome/components/bme280_base/bme280_base.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace bme280_i2c { + +static const char *const TAG = "bme280_i2c.sensor"; + +class BME280I2CComponent : public esphome::bme280_base::BME280Component, public i2c::I2CDevice { + bool read_byte(uint8_t a_register, uint8_t *data) override; + bool write_byte(uint8_t a_register, uint8_t data) override; + bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override; + bool read_byte_16(uint8_t a_register, uint16_t *data) override; + void dump_config() override; +}; + +} // namespace bme280_i2c +} // namespace esphome diff --git a/esphome/components/bme280_i2c/sensor.py b/esphome/components/bme280_i2c/sensor.py new file mode 100644 index 0000000000..489c52969d --- /dev/null +++ b/esphome/components/bme280_i2c/sensor.py @@ -0,0 +1,19 @@ +import esphome.codegen as cg +from esphome.components import i2c +from ..bme280_base.sensor import to_code as to_code_base, cv, CONFIG_SCHEMA_BASE + +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["bme280_base"] + +bme280_ns = cg.esphome_ns.namespace("bme280_i2c") +BME280I2CComponent = bme280_ns.class_( + "BME280I2CComponent", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend( + i2c.i2c_device_schema(default_address=0x77) +).extend({cv.GenerateID(): cv.declare_id(BME280I2CComponent)}) + + +async def to_code(config): + await to_code_base(config, func=i2c.register_i2c_device) diff --git a/esphome/components/bme280_spi/__init__.py b/esphome/components/bme280_spi/__init__.py new file mode 100644 index 0000000000..a1d33e4d7a --- /dev/null +++ b/esphome/components/bme280_spi/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@apbodrov"] diff --git a/esphome/components/bme280_spi/bme280_spi.cpp b/esphome/components/bme280_spi/bme280_spi.cpp new file mode 100644 index 0000000000..921128c8f5 --- /dev/null +++ b/esphome/components/bme280_spi/bme280_spi.cpp @@ -0,0 +1,66 @@ +#include +#include + +#include "bme280_spi.h" +#include + +int set_bit(uint8_t num, int position) { + int mask = 1 << position; + return num | mask; +} + +int clear_bit(uint8_t num, int position) { + int mask = 1 << position; + return num & ~mask; +} + +namespace esphome { +namespace bme280_spi { + +void BME280SPIComponent::setup() { + this->spi_setup(); + BME280Component::setup(); +}; + +// In SPI mode, only 7 bits of the register addresses are used; the MSB of register address is not used +// and replaced by a read/write bit (RW = ‘0’ for write and RW = ‘1’ for read). +// Example: address 0xF7 is accessed by using SPI register address 0x77. For write access, the byte +// 0x77 is transferred, for read access, the byte 0xF7 is transferred. +// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf + +bool BME280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) { + this->enable(); + // cause: *data = this->delegate_->transfer(tmp) doesnt work + this->delegate_->transfer(set_bit(a_register, 7)); + *data = this->delegate_->transfer(0); + this->disable(); + return true; +} + +bool BME280SPIComponent::write_byte(uint8_t a_register, uint8_t data) { + this->enable(); + this->delegate_->transfer(clear_bit(a_register, 7)); + this->delegate_->transfer(data); + this->disable(); + return true; +} + +bool BME280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) { + this->enable(); + this->delegate_->transfer(set_bit(a_register, 7)); + this->delegate_->read_array(data, len); + this->disable(); + return true; +} + +bool BME280SPIComponent::read_byte_16(uint8_t a_register, uint16_t *data) { + this->enable(); + this->delegate_->transfer(set_bit(a_register, 7)); + ((uint8_t *) data)[1] = this->delegate_->transfer(0); + ((uint8_t *) data)[0] = this->delegate_->transfer(0); + this->disable(); + return true; +} + +} // namespace bme280_spi +} // namespace esphome diff --git a/esphome/components/bme280_spi/bme280_spi.h b/esphome/components/bme280_spi/bme280_spi.h new file mode 100644 index 0000000000..b6b8997fa7 --- /dev/null +++ b/esphome/components/bme280_spi/bme280_spi.h @@ -0,0 +1,20 @@ +#pragma once + +#include "esphome/components/bme280_base/bme280_base.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace bme280_spi { + +class BME280SPIComponent : public esphome::bme280_base::BME280Component, + public spi::SPIDevice { + void setup() override; + bool read_byte(uint8_t a_register, uint8_t *data) override; + bool write_byte(uint8_t a_register, uint8_t data) override; + bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override; + bool read_byte_16(uint8_t a_register, uint16_t *data) override; +}; + +} // namespace bme280_spi +} // namespace esphome diff --git a/esphome/components/bme280_spi/sensor.py b/esphome/components/bme280_spi/sensor.py new file mode 100644 index 0000000000..3cfe1b3cdd --- /dev/null +++ b/esphome/components/bme280_spi/sensor.py @@ -0,0 +1,24 @@ +import esphome.codegen as cg +from esphome.components import spi +from esphome.components.bme280_base.sensor import ( + to_code as to_code_base, + cv, + CONFIG_SCHEMA_BASE, +) + +DEPENDENCIES = ["spi"] +AUTO_LOAD = ["bme280_base"] + + +bme280_spi_ns = cg.esphome_ns.namespace("bme280_spi") +BME280SPIComponent = bme280_spi_ns.class_( + "BME280SPIComponent", cg.PollingComponent, spi.SPIDevice +) + +CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend(spi.spi_device_schema()).extend( + {cv.GenerateID(): cv.declare_id(BME280SPIComponent)} +) + + +async def to_code(config): + await to_code_base(config, func=spi.register_spi_device) diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index 085d2a574b..15c17f4064 100644 --- a/esphome/components/bme680_bsec/__init__.py +++ b/esphome/components/bme680_bsec/__init__.py @@ -11,6 +11,7 @@ MULTI_CONF = True CONF_BME680_BSEC_ID = "bme680_bsec_id" CONF_TEMPERATURE_OFFSET = "temperature_offset" CONF_IAQ_MODE = "iaq_mode" +CONF_SUPPLY_VOLTAGE = "supply_voltage" CONF_SAMPLE_RATE = "sample_rate" CONF_STATE_SAVE_INTERVAL = "state_save_interval" @@ -22,6 +23,12 @@ IAQ_MODE_OPTIONS = { "MOBILE": IAQMode.IAQ_MODE_MOBILE, } +SupplyVoltage = bme680_bsec_ns.enum("SupplyVoltage") +SUPPLY_VOLTAGE_OPTIONS = { + "1.8V": SupplyVoltage.SUPPLY_VOLTAGE_1V8, + "3.3V": SupplyVoltage.SUPPLY_VOLTAGE_3V3, +} + SampleRate = bme680_bsec_ns.enum("SampleRate") SAMPLE_RATE_OPTIONS = { "LP": SampleRate.SAMPLE_RATE_LP, @@ -40,6 +47,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum( IAQ_MODE_OPTIONS, upper=True ), + cv.Optional(CONF_SUPPLY_VOLTAGE, default="3.3V"): cv.enum( + SUPPLY_VOLTAGE_OPTIONS, upper=True + ), cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum( SAMPLE_RATE_OPTIONS, upper=True ), @@ -67,6 +77,7 @@ async def to_code(config): cg.add(var.set_device_id(str(config[CONF_ID]))) cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET])) cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE])) + cg.add(var.set_supply_voltage(config[CONF_SUPPLY_VOLTAGE])) cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) cg.add( var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) diff --git a/esphome/components/bme680_bsec/bme680_bsec.cpp b/esphome/components/bme680_bsec/bme680_bsec.cpp index 2b1b0dc948..17dae35b5c 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.cpp +++ b/esphome/components/bme680_bsec/bme680_bsec.cpp @@ -52,17 +52,33 @@ void BME680BSECComponent::setup() { void BME680BSECComponent::set_config_() { if (this->sample_rate_ == SAMPLE_RATE_ULP) { - const uint8_t config[] = { + if (this->supply_voltage_ == SUPPLY_VOLTAGE_3V3) { + const uint8_t config[] = { #include "config/generic_33v_300s_28d/bsec_iaq.txt" - }; - this->bsec_status_ = - bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); - } else { - const uint8_t config[] = { + }; + this->bsec_status_ = + bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); + } else { // SUPPLY_VOLTAGE_1V8 + const uint8_t config[] = { +#include "config/generic_18v_300s_28d/bsec_iaq.txt" + }; + this->bsec_status_ = + bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); + } + } else { // SAMPLE_RATE_LP + if (this->supply_voltage_ == SUPPLY_VOLTAGE_3V3) { + const uint8_t config[] = { #include "config/generic_33v_3s_28d/bsec_iaq.txt" - }; - this->bsec_status_ = - bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); + }; + this->bsec_status_ = + bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); + } else { // SUPPLY_VOLTAGE_1V8 + const uint8_t config[] = { +#include "config/generic_18v_3s_28d/bsec_iaq.txt" + }; + this->bsec_status_ = + bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); + } } } @@ -145,6 +161,7 @@ void BME680BSECComponent::dump_config() { ESP_LOGCONFIG(TAG, " Temperature Offset: %.2f", this->temperature_offset_); ESP_LOGCONFIG(TAG, " IAQ Mode: %s", this->iaq_mode_ == IAQ_MODE_STATIC ? "Static" : "Mobile"); + ESP_LOGCONFIG(TAG, " Supply Voltage: %sV", this->supply_voltage_ == SUPPLY_VOLTAGE_3V3 ? "3.3" : "1.8"); ESP_LOGCONFIG(TAG, " Sample Rate: %s", BME680_BSEC_SAMPLE_RATE_LOG(this->sample_rate_)); ESP_LOGCONFIG(TAG, " State Save Interval: %ims", this->state_save_interval_ms_); diff --git a/esphome/components/bme680_bsec/bme680_bsec.h b/esphome/components/bme680_bsec/bme680_bsec.h index a97ad2f53e..e52dbe964b 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.h +++ b/esphome/components/bme680_bsec/bme680_bsec.h @@ -21,6 +21,11 @@ enum IAQMode { IAQ_MODE_MOBILE = 1, }; +enum SupplyVoltage { + SUPPLY_VOLTAGE_3V3 = 0, + SUPPLY_VOLTAGE_1V8 = 1, +}; + enum SampleRate { SAMPLE_RATE_LP = 0, SAMPLE_RATE_ULP = 1, @@ -35,6 +40,7 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { void set_temperature_offset(float offset) { this->temperature_offset_ = offset; } void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; } void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; } + void set_supply_voltage(SupplyVoltage supply_voltage) { this->supply_voltage_ = supply_voltage; } void set_sample_rate(SampleRate sample_rate) { this->sample_rate_ = sample_rate; } void set_temperature_sample_rate(SampleRate sample_rate) { this->temperature_sample_rate_ = sample_rate; } @@ -109,6 +115,7 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { std::string device_id_; float temperature_offset_{0}; IAQMode iaq_mode_{IAQ_MODE_STATIC}; + SupplyVoltage supply_voltage_; SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT}; diff --git a/esphome/components/bmp280/bmp280.cpp b/esphome/components/bmp280/bmp280.cpp index a5b2517893..c92daa07fb 100644 --- a/esphome/components/bmp280/bmp280.cpp +++ b/esphome/components/bmp280/bmp280.cpp @@ -200,8 +200,8 @@ float BMP280Component::read_temperature_(int32_t *t_fine) { int32_t var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14; *t_fine = var1 + var2; - float temperature = (*t_fine * 5 + 128) >> 8; - return temperature / 100.0f; + float temperature = (*t_fine * 5 + 128); + return temperature / 25600.0f; } float BMP280Component::read_pressure_(int32_t t_fine) { diff --git a/esphome/components/combination/__init__.py b/esphome/components/combination/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/combination/combination.cpp b/esphome/components/combination/combination.cpp new file mode 100644 index 0000000000..716d270390 --- /dev/null +++ b/esphome/components/combination/combination.cpp @@ -0,0 +1,262 @@ +#include "combination.h" + +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +#include +#include +#include + +namespace esphome { +namespace combination { + +static const char *const TAG = "combination"; + +void CombinationComponent::log_config_(const LogString *combo_type) { + LOG_SENSOR("", "Combination Sensor:", this); + ESP_LOGCONFIG(TAG, " Combination Type: %s", LOG_STR_ARG(combo_type)); + this->log_source_sensors(); +} + +void CombinationNoParameterComponent::add_source(Sensor *sensor) { this->sensors_.emplace_back(sensor); } + +void CombinationOneParameterComponent::add_source(Sensor *sensor, std::function const &stddev) { + this->sensor_pairs_.emplace_back(sensor, stddev); +} + +void CombinationOneParameterComponent::add_source(Sensor *sensor, float stddev) { + this->add_source(sensor, std::function{[stddev](float x) -> float { return stddev; }}); +} + +void CombinationNoParameterComponent::log_source_sensors() { + ESP_LOGCONFIG(TAG, " Source Sensors:"); + for (const auto &sensor : this->sensors_) { + ESP_LOGCONFIG(TAG, " - %s", sensor->get_name().c_str()); + } +} + +void CombinationOneParameterComponent::log_source_sensors() { + ESP_LOGCONFIG(TAG, " Source Sensors:"); + for (const auto &sensor : this->sensor_pairs_) { + auto &entity = *sensor.first; + ESP_LOGCONFIG(TAG, " - %s", entity.get_name().c_str()); + } +} + +void CombinationNoParameterComponent::setup() { + for (const auto &sensor : this->sensors_) { + // All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result + // repeatedly in the same loop if multiple source senors update. + sensor->add_on_state_callback( + [this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); }); + } +} + +void KalmanCombinationComponent::dump_config() { + this->log_config_(LOG_STR("kalman")); + ESP_LOGCONFIG(TAG, " Update variance: %f per ms", this->update_variance_value_); + + if (this->std_dev_sensor_ != nullptr) { + LOG_SENSOR(" ", "Standard Deviation Sensor:", this->std_dev_sensor_); + } +} + +void KalmanCombinationComponent::setup() { + for (const auto &sensor : this->sensor_pairs_) { + const auto stddev = sensor.second; + sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); }); + } +} + +void KalmanCombinationComponent::update_variance_() { + uint32_t now = millis(); + + // Variance increases by update_variance_ each millisecond + auto dt = now - this->last_update_; + auto dv = this->update_variance_value_ * dt; + this->variance_ += dv; + this->last_update_ = now; +} + +void KalmanCombinationComponent::correct_(float value, float stddev) { + if (std::isnan(value) || std::isinf(stddev)) { + return; + } + + if (std::isnan(this->state_) || std::isinf(this->variance_)) { + this->state_ = value; + this->variance_ = stddev * stddev; + if (this->std_dev_sensor_ != nullptr) { + this->std_dev_sensor_->publish_state(stddev); + } + return; + } + + this->update_variance_(); + + // Combine two gaussian distributions mu1+-var1, mu2+-var2 to a new one around mu + // Use the value with the smaller variance as mu1 to prevent precision errors + const bool this_first = this->variance_ < (stddev * stddev); + const float mu1 = this_first ? this->state_ : value; + const float mu2 = this_first ? value : this->state_; + + const float var1 = this_first ? this->variance_ : stddev * stddev; + const float var2 = this_first ? stddev * stddev : this->variance_; + + const float mu = mu1 + var1 * (mu2 - mu1) / (var1 + var2); + const float var = var1 - (var1 * var1) / (var1 + var2); + + // Update and publish state + this->state_ = mu; + this->variance_ = var; + + this->publish_state(mu); + if (this->std_dev_sensor_ != nullptr) { + this->std_dev_sensor_->publish_state(std::sqrt(var)); + } +} + +void LinearCombinationComponent::setup() { + for (const auto &sensor : this->sensor_pairs_) { + // All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result + // repeatedly in the same loop if multiple source senors update. + sensor.first->add_on_state_callback( + [this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); }); + } +} + +void LinearCombinationComponent::handle_new_value(float value) { + // Multiplies each sensor state by a configured coeffecient and then sums + + if (!std::isfinite(value)) + return; + + float sum = 0.0; + + for (const auto &sensor : this->sensor_pairs_) { + const float sensor_state = sensor.first->state; + if (std::isfinite(sensor_state)) { + sum += sensor_state * sensor.second(sensor_state); + } + } + + this->publish_state(sum); +}; + +void MaximumCombinationComponent::handle_new_value(float value) { + if (!std::isfinite(value)) + return; + + float max_value = (-1) * std::numeric_limits::infinity(); // note x = max(x, -infinity) + + for (const auto &sensor : this->sensors_) { + if (std::isfinite(sensor->state)) { + max_value = std::max(max_value, sensor->state); + } + } + + this->publish_state(max_value); +} + +void MeanCombinationComponent::handle_new_value(float value) { + if (!std::isfinite(value)) + return; + + float sum = 0.0; + size_t count = 0.0; + + for (const auto &sensor : this->sensors_) { + if (std::isfinite(sensor->state)) { + ++count; + sum += sensor->state; + } + } + + float mean = sum / count; + + this->publish_state(mean); +} + +void MedianCombinationComponent::handle_new_value(float value) { + // Sorts sensor states in ascending order and determines the middle value + + if (!std::isfinite(value)) + return; + + std::vector sensor_states; + for (const auto &sensor : this->sensors_) { + if (std::isfinite(sensor->state)) { + sensor_states.push_back(sensor->state); + } + } + + sort(sensor_states.begin(), sensor_states.end()); + size_t sensor_states_size = sensor_states.size(); + + float median = NAN; + + if (sensor_states_size) { + if (sensor_states_size % 2) { + // Odd number of measurements, use middle measurement + median = sensor_states[sensor_states_size / 2]; + } else { + // Even number of measurements, use the average of the two middle measurements + median = (sensor_states[sensor_states_size / 2] + sensor_states[sensor_states_size / 2 - 1]) / 2.0; + } + } + + this->publish_state(median); +} + +void MinimumCombinationComponent::handle_new_value(float value) { + if (!std::isfinite(value)) + return; + + float min_value = std::numeric_limits::infinity(); // note x = min(x, infinity) + + for (const auto &sensor : this->sensors_) { + if (std::isfinite(sensor->state)) { + min_value = std::min(min_value, sensor->state); + } + } + + this->publish_state(min_value); +} + +void MostRecentCombinationComponent::handle_new_value(float value) { this->publish_state(value); } + +void RangeCombinationComponent::handle_new_value(float value) { + // Sorts sensor states then takes difference between largest and smallest states + + if (!std::isfinite(value)) + return; + + std::vector sensor_states; + for (const auto &sensor : this->sensors_) { + if (std::isfinite(sensor->state)) { + sensor_states.push_back(sensor->state); + } + } + + sort(sensor_states.begin(), sensor_states.end()); + + float range = sensor_states.back() - sensor_states.front(); + this->publish_state(range); +} + +void SumCombinationComponent::handle_new_value(float value) { + if (!std::isfinite(value)) + return; + + float sum = 0.0; + for (const auto &sensor : this->sensors_) { + if (std::isfinite(sensor->state)) { + sum += sensor->state; + } + } + + this->publish_state(sum); +} + +} // namespace combination +} // namespace esphome diff --git a/esphome/components/combination/combination.h b/esphome/components/combination/combination.h new file mode 100644 index 0000000000..901aeaf259 --- /dev/null +++ b/esphome/components/combination/combination.h @@ -0,0 +1,141 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" + +#include + +namespace esphome { +namespace combination { + +class CombinationComponent : public Component, public sensor::Sensor { + public: + float get_setup_priority() const override { return esphome::setup_priority::DATA; } + + /// @brief Logs all source sensor's names + virtual void log_source_sensors() = 0; + + protected: + /// @brief Logs the sensor for use in dump_config + /// @param combo_type Name of the combination operation + void log_config_(const LogString *combo_type); +}; + +/// @brief Base class for operations that do not require an extra parameter to compute the combination +class CombinationNoParameterComponent : public CombinationComponent { + public: + /// @brief Adds a callback to each source sensor + void setup() override; + + void add_source(Sensor *sensor); + + /// @brief Computes the combination + /// @param value Newest sensor measurement + virtual void handle_new_value(float value) = 0; + + /// @brief Logs all source sensor's names in sensors_ + void log_source_sensors() override; + + protected: + std::vector sensors_; +}; + +// Base class for opertions that require one parameter to compute the combination +class CombinationOneParameterComponent : public CombinationComponent { + public: + void add_source(Sensor *sensor, std::function const &stddev); + void add_source(Sensor *sensor, float stddev); + + /// @brief Logs all source sensor's names in sensor_pairs_ + void log_source_sensors() override; + + protected: + std::vector>> sensor_pairs_; +}; + +class KalmanCombinationComponent : public CombinationOneParameterComponent { + public: + void dump_config() override; + void setup() override; + + void set_process_std_dev(float process_std_dev) { + this->update_variance_value_ = process_std_dev * process_std_dev * 0.001f; + } + void set_std_dev_sensor(Sensor *sensor) { this->std_dev_sensor_ = sensor; } + + protected: + void update_variance_(); + void correct_(float value, float stddev); + + // Optional sensor for publishing the current error + sensor::Sensor *std_dev_sensor_{nullptr}; + + // Tick of the last update + uint32_t last_update_{0}; + // Change of the variance, per ms + float update_variance_value_{0.f}; + + // Best guess for the state and its variance + float state_{NAN}; + float variance_{INFINITY}; +}; + +class LinearCombinationComponent : public CombinationOneParameterComponent { + public: + void dump_config() override { this->log_config_(LOG_STR("linear")); } + void setup() override; + + void handle_new_value(float value); +}; + +class MaximumCombinationComponent : public CombinationNoParameterComponent { + public: + void dump_config() override { this->log_config_(LOG_STR("max")); } + + void handle_new_value(float value) override; +}; + +class MeanCombinationComponent : public CombinationNoParameterComponent { + public: + void dump_config() override { this->log_config_(LOG_STR("mean")); } + + void handle_new_value(float value) override; +}; + +class MedianCombinationComponent : public CombinationNoParameterComponent { + public: + void dump_config() override { this->log_config_(LOG_STR("median")); } + + void handle_new_value(float value) override; +}; + +class MinimumCombinationComponent : public CombinationNoParameterComponent { + public: + void dump_config() override { this->log_config_(LOG_STR("min")); } + + void handle_new_value(float value) override; +}; + +class MostRecentCombinationComponent : public CombinationNoParameterComponent { + public: + void dump_config() override { this->log_config_(LOG_STR("most_recently_updated")); } + + void handle_new_value(float value) override; +}; + +class RangeCombinationComponent : public CombinationNoParameterComponent { + public: + void dump_config() override { this->log_config_(LOG_STR("range")); } + + void handle_new_value(float value) override; +}; + +class SumCombinationComponent : public CombinationNoParameterComponent { + public: + void dump_config() override { this->log_config_(LOG_STR("sum")); } + + void handle_new_value(float value) override; +}; + +} // namespace combination +} // namespace esphome diff --git a/esphome/components/combination/sensor.py b/esphome/components/combination/sensor.py new file mode 100644 index 0000000000..fad0277061 --- /dev/null +++ b/esphome/components/combination/sensor.py @@ -0,0 +1,176 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_ACCURACY_DECIMALS, + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_RANGE, + CONF_SOURCE, + CONF_SUM, + CONF_TYPE, + CONF_UNIT_OF_MEASUREMENT, +) +from esphome.core.entity_helpers import inherit_property_from + +CODEOWNERS = ["@Cat-Ion", "@kahrendt"] + +combination_ns = cg.esphome_ns.namespace("combination") + +KalmanCombinationComponent = combination_ns.class_( + "KalmanCombinationComponent", cg.Component, sensor.Sensor +) +LinearCombinationComponent = combination_ns.class_( + "LinearCombinationComponent", cg.Component, sensor.Sensor +) +MaximumCombinationComponent = combination_ns.class_( + "MaximumCombinationComponent", cg.Component, sensor.Sensor +) +MeanCombinationComponent = combination_ns.class_( + "MeanCombinationComponent", cg.Component, sensor.Sensor +) +MedianCombinationComponent = combination_ns.class_( + "MedianCombinationComponent", cg.Component, sensor.Sensor +) +MinimumCombinationComponent = combination_ns.class_( + "MinimumCombinationComponent", cg.Component, sensor.Sensor +) +MostRecentCombinationComponent = combination_ns.class_( + "MostRecentCombinationComponent", cg.Component, sensor.Sensor +) +RangeCombinationComponent = combination_ns.class_( + "RangeCombinationComponent", cg.Component, sensor.Sensor +) +SumCombinationComponent = combination_ns.class_( + "SumCombinationComponent", cg.Component, sensor.Sensor +) + +CONF_COEFFECIENT = "coeffecient" +CONF_ERROR = "error" +CONF_KALMAN = "kalman" +CONF_LINEAR = "linear" +CONF_MAX = "max" +CONF_MEAN = "mean" +CONF_MEDIAN = "median" +CONF_MIN = "min" +CONF_MOST_RECENTLY_UPDATED = "most_recently_updated" +CONF_PROCESS_STD_DEV = "process_std_dev" +CONF_SOURCES = "sources" +CONF_STD_DEV = "std_dev" + + +KALMAN_SOURCE_SCHEMA = cv.Schema( + { + cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor), + cv.Required(CONF_ERROR): cv.templatable(cv.positive_float), + } +) + +LINEAR_SOURCE_SCHEMA = cv.Schema( + { + cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor), + cv.Required(CONF_COEFFECIENT): cv.templatable(cv.float_), + } +) + +SENSOR_ONLY_SOURCE_SCHEMA = cv.Schema( + { + cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor), + } +) + +CONFIG_SCHEMA = cv.typed_schema( + { + CONF_KALMAN: sensor.sensor_schema(KalmanCombinationComponent) + .extend(cv.COMPONENT_SCHEMA) + .extend( + { + cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float, + cv.Required(CONF_SOURCES): cv.ensure_list(KALMAN_SOURCE_SCHEMA), + cv.Optional(CONF_STD_DEV): sensor.sensor_schema(), + } + ), + CONF_LINEAR: sensor.sensor_schema(LinearCombinationComponent) + .extend(cv.COMPONENT_SCHEMA) + .extend({cv.Required(CONF_SOURCES): cv.ensure_list(LINEAR_SOURCE_SCHEMA)}), + CONF_MAX: sensor.sensor_schema(MaximumCombinationComponent) + .extend(cv.COMPONENT_SCHEMA) + .extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}), + CONF_MEAN: sensor.sensor_schema(MeanCombinationComponent) + .extend(cv.COMPONENT_SCHEMA) + .extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}), + CONF_MEDIAN: sensor.sensor_schema(MedianCombinationComponent) + .extend(cv.COMPONENT_SCHEMA) + .extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}), + CONF_MIN: sensor.sensor_schema(MinimumCombinationComponent) + .extend(cv.COMPONENT_SCHEMA) + .extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}), + CONF_MOST_RECENTLY_UPDATED: sensor.sensor_schema(MostRecentCombinationComponent) + .extend(cv.COMPONENT_SCHEMA) + .extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}), + CONF_RANGE: sensor.sensor_schema(RangeCombinationComponent) + .extend(cv.COMPONENT_SCHEMA) + .extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}), + CONF_SUM: sensor.sensor_schema(SumCombinationComponent) + .extend(cv.COMPONENT_SCHEMA) + .extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}), + } +) + + +# Inherit some sensor values from the first source, for both the state and the error value +# CONF_STATE_CLASS could also be inherited, but might lead to unexpected behaviour with "total_increasing" +properties_to_inherit = [ + CONF_ACCURACY_DECIMALS, + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_UNIT_OF_MEASUREMENT, +] +inherit_schema_for_state = [ + inherit_property_from(property, [CONF_SOURCES, 0, CONF_SOURCE]) + for property in properties_to_inherit +] +inherit_schema_for_std_dev = [ + inherit_property_from([CONF_STD_DEV, property], [CONF_SOURCES, 0, CONF_SOURCE]) + for property in properties_to_inherit +] + +FINAL_VALIDATE_SCHEMA = cv.All( + *inherit_schema_for_state, + *inherit_schema_for_std_dev, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + + if proces_std_dev := config.get(CONF_PROCESS_STD_DEV): + cg.add(var.set_process_std_dev(proces_std_dev)) + + for source_conf in config[CONF_SOURCES]: + source = await cg.get_variable(source_conf[CONF_SOURCE]) + if config[CONF_TYPE] == CONF_KALMAN: + error = await cg.templatable( + source_conf[CONF_ERROR], + [(float, "x")], + cg.float_, + ) + cg.add(var.add_source(source, error)) + elif config[CONF_TYPE] == CONF_LINEAR: + coeffecient = await cg.templatable( + source_conf[CONF_COEFFECIENT], + [(float, "x")], + cg.float_, + ) + cg.add(var.add_source(source, coeffecient)) + else: + cg.add(var.add_source(source)) + + if CONF_STD_DEV in config: + sens = await sensor.new_sensor(config[CONF_STD_DEV]) + cg.add(var.set_std_dev_sensor(sens)) diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 60132fd98f..f1420aa127 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -1,6 +1,8 @@ #include "cse7766.h" #include "esphome/core/log.h" #include +#include +#include namespace esphome { namespace cse7766 { @@ -68,20 +70,26 @@ bool CSE7766Component::check_byte_() { return true; } void CSE7766Component::parse_data_() { - ESP_LOGVV(TAG, "CSE7766 Data: "); - for (uint8_t i = 0; i < 23; i++) { - ESP_LOGVV(TAG, " %u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i + 1, BYTE_TO_BINARY(this->raw_data_[i]), - this->raw_data_[i]); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + { + std::stringstream ss; + ss << "Raw data:" << std::hex << std::uppercase << std::setfill('0'); + for (uint8_t i = 0; i < 23; i++) { + ss << ' ' << std::setw(2) << static_cast(this->raw_data_[i]); + } + ESP_LOGVV(TAG, "%s", ss.str().c_str()); } +#endif + // Parse header uint8_t header1 = this->raw_data_[0]; + if (header1 == 0xAA) { ESP_LOGE(TAG, "CSE7766 not calibrated!"); return; } bool power_cycle_exceeds_range = false; - if ((header1 & 0xF0) == 0xF0) { if (header1 & 0xD) { ESP_LOGE(TAG, "CSE7766 reports abnormal external circuit or chip damage: (0x%02X)", header1); @@ -94,99 +102,122 @@ void CSE7766Component::parse_data_() { if (header1 & (1 << 0)) { ESP_LOGE(TAG, " Coefficient storage area is abnormal."); } + + // Datasheet: voltage or current cycle exceeding range means invalid values return; } power_cycle_exceeds_range = header1 & (1 << 1); } - uint32_t voltage_calib = this->get_24_bit_uint_(2); + // Parse data frame + uint32_t voltage_coeff = this->get_24_bit_uint_(2); uint32_t voltage_cycle = this->get_24_bit_uint_(5); - uint32_t current_calib = this->get_24_bit_uint_(8); + uint32_t current_coeff = this->get_24_bit_uint_(8); uint32_t current_cycle = this->get_24_bit_uint_(11); - uint32_t power_calib = this->get_24_bit_uint_(14); + uint32_t power_coeff = this->get_24_bit_uint_(14); uint32_t power_cycle = this->get_24_bit_uint_(17); - uint8_t adj = this->raw_data_[20]; - uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22]; - - bool have_voltage = adj & 0x40; - if (have_voltage) { - // voltage cycle of serial port outputted is a complete cycle; - this->voltage_acc_ += voltage_calib / float(voltage_cycle); - this->voltage_counts_ += 1; - } + uint16_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22]; bool have_power = adj & 0x10; - float power = 0.0f; + bool have_current = adj & 0x20; + bool have_voltage = adj & 0x40; - if (have_power) { - // power cycle of serial port outputted is a complete cycle; - // According to the user manual, power cycle exceeding range means the measured power is 0 - if (!power_cycle_exceeds_range) { - power = power_calib / float(power_cycle); + float voltage = 0.0f; + if (have_voltage) { + voltage = voltage_coeff / float(voltage_cycle); + if (this->voltage_sensor_ != nullptr) { + this->voltage_sensor_->publish_state(voltage); } - this->power_acc_ += power; - this->power_counts_ += 1; + } - uint32_t difference; - if (this->cf_pulses_last_ == 0) { + float energy = 0.0; + if (this->energy_sensor_ != nullptr) { + if (this->cf_pulses_last_ == 0 && !this->energy_sensor_->has_state()) { this->cf_pulses_last_ = cf_pulses; } - - if (cf_pulses < this->cf_pulses_last_) { - difference = cf_pulses + (0x10000 - this->cf_pulses_last_); - } else { - difference = cf_pulses - this->cf_pulses_last_; - } + uint16_t cf_diff = cf_pulses - this->cf_pulses_last_; + this->cf_pulses_total_ += cf_diff; this->cf_pulses_last_ = cf_pulses; - this->energy_total_ += difference * float(power_calib) / 1000000.0f / 3600.0f; - this->energy_total_counts_ += 1; + energy = this->cf_pulses_total_ * float(power_coeff) / 1000000.0f / 3600.0f; + this->energy_sensor_->publish_state(energy); } - if (adj & 0x20) { - // indicates current cycle of serial port outputted is a complete cycle; - float current = 0.0f; - if (have_voltage && !have_power) { - // Testing has shown that when we have voltage and current but not power, that means the power is 0. - // We report a power of 0, which in turn means we should report a current of 0. - this->power_counts_ += 1; - } else if (power != 0.0f) { - current = current_calib / float(current_cycle); + float power = 0.0f; + if (power_cycle_exceeds_range) { + // Datasheet: power cycle exceeding range means active power is 0 + if (this->power_sensor_ != nullptr) { + this->power_sensor_->publish_state(0.0f); + } + } else if (have_power) { + power = power_coeff / float(power_cycle); + if (this->power_sensor_ != nullptr) { + this->power_sensor_->publish_state(power); } - this->current_acc_ += current; - this->current_counts_ += 1; } -} -void CSE7766Component::update() { - const auto publish_state = [](const char *name, sensor::Sensor *sensor, float &acc, uint32_t &counts) { - if (counts != 0) { - const auto avg = acc / counts; - ESP_LOGV(TAG, "Got %s_acc=%.2f %s_counts=%" PRIu32 " %s=%.1f", name, acc, name, counts, name, avg); + float current = 0.0f; + float calculated_current = 0.0f; + if (have_current) { + // Assumption: if we don't have power measurement, then current is likely below 50mA + if (have_power && voltage > 1.0f) { + calculated_current = power / voltage; + } + // Datasheet: minimum measured current is 50mA + if (calculated_current > 0.05f) { + current = current_coeff / float(current_cycle); + } + if (this->current_sensor_ != nullptr) { + this->current_sensor_->publish_state(current); + } + } - if (sensor != nullptr) { - sensor->publish_state(avg); + if (have_voltage && have_current) { + const float apparent_power = voltage * current; + if (this->apparent_power_sensor_ != nullptr) { + this->apparent_power_sensor_->publish_state(apparent_power); + } + if (this->power_factor_sensor_ != nullptr && (have_power || power_cycle_exceeds_range)) { + float pf = NAN; + if (apparent_power > 0) { + pf = power / apparent_power; + if (pf < 0 || pf > 1) { + ESP_LOGD(TAG, "Impossible power factor: %.4f not in interval [0, 1]", pf); + pf = NAN; + } + } else if (apparent_power == 0 && power == 0) { + // No load, report ideal power factor + pf = 1.0f; + } else if (current == 0 && calculated_current <= 0.05f) { + // Datasheet: minimum measured current is 50mA + ESP_LOGV(TAG, "Can't calculate power factor (current below minimum for CSE7766)"); + } else { + ESP_LOGW(TAG, "Can't calculate power factor from P = %.4f W, S = %.4f VA", power, apparent_power); } - - acc = 0.0f; - counts = 0; + this->power_factor_sensor_->publish_state(pf); } - }; - - publish_state("voltage", this->voltage_sensor_, this->voltage_acc_, this->voltage_counts_); - publish_state("current", this->current_sensor_, this->current_acc_, this->current_counts_); - publish_state("power", this->power_sensor_, this->power_acc_, this->power_counts_); - - if (this->energy_total_counts_ != 0) { - ESP_LOGV(TAG, "Got energy_total=%.2f energy_total_counts=%" PRIu32, this->energy_total_, - this->energy_total_counts_); - - if (this->energy_sensor_ != nullptr) { - this->energy_sensor_->publish_state(this->energy_total_); - } - this->energy_total_counts_ = 0; } + +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + { + std::stringstream ss; + ss << "Parsed:"; + if (have_voltage) { + ss << " V=" << voltage << "V"; + } + if (have_current) { + ss << " I=" << current * 1000.0f << "mA (~" << calculated_current * 1000.0f << "mA)"; + } + if (have_power) { + ss << " P=" << power << "W"; + } + if (energy != 0.0f) { + ss << " E=" << energy << "kWh (" << cf_pulses << ")"; + } + ESP_LOGVV(TAG, "%s", ss.str().c_str()); + } +#endif } uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) { @@ -196,11 +227,12 @@ uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) { void CSE7766Component::dump_config() { ESP_LOGCONFIG(TAG, "CSE7766:"); - LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_); LOG_SENSOR(" ", "Power", this->power_sensor_); LOG_SENSOR(" ", "Energy", this->energy_sensor_); + LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_); + LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_); this->check_uart_settings(4800); } diff --git a/esphome/components/cse7766/cse7766.h b/esphome/components/cse7766/cse7766.h index 2f30eec09f..0b724d6bbb 100644 --- a/esphome/components/cse7766/cse7766.h +++ b/esphome/components/cse7766/cse7766.h @@ -7,16 +7,19 @@ namespace esphome { namespace cse7766 { -class CSE7766Component : public PollingComponent, public uart::UARTDevice { +class CSE7766Component : public Component, public uart::UARTDevice { public: void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } + void set_apparent_power_sensor(sensor::Sensor *apparent_power_sensor) { + apparent_power_sensor_ = apparent_power_sensor; + } + void set_power_factor_sensor(sensor::Sensor *power_factor_sensor) { power_factor_sensor_ = power_factor_sensor; } void loop() override; float get_setup_priority() const override; - void update() override; void dump_config() override; protected: @@ -31,16 +34,10 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr}; sensor::Sensor *energy_sensor_{nullptr}; - float voltage_acc_{0.0f}; - float current_acc_{0.0f}; - float power_acc_{0.0f}; - float energy_total_{0.0f}; - uint32_t cf_pulses_last_{0}; - uint32_t voltage_counts_{0}; - uint32_t current_counts_{0}; - uint32_t power_counts_{0}; - // Setting this to 1 means it will always publish 0 once at startup - uint32_t energy_total_counts_{1}; + sensor::Sensor *apparent_power_sensor_{nullptr}; + sensor::Sensor *power_factor_sensor_{nullptr}; + uint32_t cf_pulses_total_{0}; + uint16_t cf_pulses_last_{0}; }; } // namespace cse7766 diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index d98b351287..b64dcf7de3 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -2,19 +2,24 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, uart from esphome.const import ( + CONF_APPARENT_POWER, CONF_CURRENT, CONF_ENERGY, CONF_ID, CONF_POWER, + CONF_POWER_FACTOR, CONF_VOLTAGE, + DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, + DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, STATE_CLASS_TOTAL_INCREASING, - UNIT_VOLT, UNIT_AMPERE, + UNIT_VOLT, + UNIT_VOLT_AMPS, UNIT_WATT, UNIT_WATT_HOURS, ) @@ -22,43 +27,48 @@ from esphome.const import ( DEPENDENCIES = ["uart"] cse7766_ns = cg.esphome_ns.namespace("cse7766") -CSE7766Component = cse7766_ns.class_( - "CSE7766Component", cg.PollingComponent, uart.UARTDevice -) +CSE7766Component = cse7766_ns.class_("CSE7766Component", cg.Component, uart.UARTDevice) -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(CSE7766Component), - cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, - device_class=DEVICE_CLASS_VOLTAGE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_CURRENT): sensor.sensor_schema( - unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=2, - device_class=DEVICE_CLASS_CURRENT, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_POWER): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT, - accuracy_decimals=1, - device_class=DEVICE_CLASS_POWER, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_ENERGY): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, - accuracy_decimals=3, - device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_TOTAL_INCREASING, - ), - } - ) - .extend(cv.polling_component_schema("60s")) - .extend(uart.UART_DEVICE_SCHEMA) -) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CSE7766Component), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT_AMPS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, + ), + } +).extend(uart.UART_DEVICE_SCHEMA) FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( "cse7766", baud_rate=4800, require_rx=True ) @@ -81,3 +91,9 @@ async def to_code(config): if energy_config := config.get(CONF_ENERGY): sens = await sensor.new_sensor(energy_config) cg.add(var.set_energy_sensor(sens)) + if apparent_power_config := config.get(CONF_APPARENT_POWER): + sens = await sensor.new_sensor(apparent_power_config) + cg.add(var.set_apparent_power_sensor(sens)) + if power_factor_config := config.get(CONF_POWER_FACTOR): + sens = await sensor.new_sensor(power_factor_config) + cg.add(var.set_power_factor_sensor(sens)) diff --git a/esphome/components/cst226/__init__.py b/esphome/components/cst226/__init__.py new file mode 100644 index 0000000000..847e44dbda --- /dev/null +++ b/esphome/components/cst226/__init__.py @@ -0,0 +1,6 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@clydebarrow"] +DEPENDENCIES = ["i2c"] + +cst226_ns = cg.esphome_ns.namespace("cst226") diff --git a/esphome/components/cst226/touchscreen/__init__.py b/esphome/components/cst226/touchscreen/__init__.py new file mode 100644 index 0000000000..76975ffe78 --- /dev/null +++ b/esphome/components/cst226/touchscreen/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome import pins +from esphome.components import i2c, touchscreen +from esphome.const import CONF_INTERRUPT_PIN, CONF_ID, CONF_RESET_PIN +from .. import cst226_ns + + +CST226Touchscreen = cst226_ns.class_( + "CST226Touchscreen", + touchscreen.Touchscreen, + i2c.I2CDevice, +) + +CST226ButtonListener = cst226_ns.class_("CST226ButtonListener") +CONFIG_SCHEMA = ( + touchscreen.touchscreen_schema("100ms") + .extend( + { + cv.GenerateID(): cv.declare_id(CST226Touchscreen), + cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(i2c.i2c_device_schema(0x5A)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await touchscreen.register_touchscreen(var, config) + await i2c.register_i2c_device(var, config) + + if interrupt_pin := config.get(CONF_INTERRUPT_PIN): + cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) + if reset_pin := config.get(CONF_RESET_PIN): + cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin))) diff --git a/esphome/components/cst226/touchscreen/cst226_touchscreen.cpp b/esphome/components/cst226/touchscreen/cst226_touchscreen.cpp new file mode 100644 index 0000000000..d4e43d30f5 --- /dev/null +++ b/esphome/components/cst226/touchscreen/cst226_touchscreen.cpp @@ -0,0 +1,92 @@ +#include "cst226_touchscreen.h" + +namespace esphome { +namespace cst226 { + +void CST226Touchscreen::setup() { + esph_log_config(TAG, "Setting up CST226 Touchscreen..."); + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->reset_pin_->digital_write(true); + delay(5); + this->reset_pin_->digital_write(false); + delay(5); + this->reset_pin_->digital_write(true); + this->set_timeout(30, [this] { this->continue_setup_(); }); + } else { + this->continue_setup_(); + } +} + +void CST226Touchscreen::update_touches() { + uint8_t data[28]; + if (!this->read_bytes(CST226_REG_STATUS, data, sizeof data)) { + this->status_set_warning(); + this->skip_update_ = true; + return; + } + this->status_clear_warning(); + if (data[6] != 0xAB || data[0] == 0xAB || data[5] == 0x80) { + this->skip_update_ = true; + return; + } + uint8_t num_of_touches = data[5] & 0x7F; + if (num_of_touches == 0 || num_of_touches > 5) { + this->write_byte(0, 0xAB); + return; + } + + size_t index = 0; + for (uint8_t i = 0; i != num_of_touches; i++) { + uint8_t id = data[index] >> 4; + int16_t x = (data[index + 1] << 4) | ((data[index + 3] >> 4) & 0x0F); + 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); + esph_log_v(TAG, "Read touch %d: %d/%d", id, x, y); + index += 5; + if (i == 0) + index += 2; + } +} + +void CST226Touchscreen::continue_setup_() { + uint8_t buffer[8]; + if (this->interrupt_pin_ != nullptr) { + this->interrupt_pin_->setup(); + this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); + } + buffer[0] = 0xD1; + if (this->write_register16(0xD1, buffer, 1) != i2c::ERROR_OK) { + esph_log_e(TAG, "Write byte to 0xD1 failed"); + this->mark_failed(); + return; + } + delay(10); + if (this->read16_(0xD204, buffer, 4)) { + uint16_t chip_id = buffer[2] + (buffer[3] << 8); + uint16_t project_id = buffer[0] + (buffer[1] << 8); + 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)) { + this->x_raw_max_ = buffer[0] + (buffer[1] << 8); + this->y_raw_max_ = buffer[2] + (buffer[3] << 8); + } else { + this->x_raw_max_ = this->display_->get_native_width(); + this->y_raw_max_ = this->display_->get_native_height(); + } + } + this->setup_complete_ = true; + esph_log_config(TAG, "CST226 Touchscreen setup complete"); +} + +void CST226Touchscreen::dump_config() { + ESP_LOGCONFIG(TAG, "CST226 Touchscreen:"); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); +} + +} // namespace cst226 +} // namespace esphome diff --git a/esphome/components/cst226/touchscreen/cst226_touchscreen.h b/esphome/components/cst226/touchscreen/cst226_touchscreen.h new file mode 100644 index 0000000000..9f518e5068 --- /dev/null +++ b/esphome/components/cst226/touchscreen/cst226_touchscreen.h @@ -0,0 +1,44 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace cst226 { + +static const char *const TAG = "cst226.touchscreen"; + +static const uint8_t CST226_REG_STATUS = 0x00; + +class CST226Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice { + public: + void setup() override; + void update_touches() override; + void dump_config() override; + + 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(); } + + protected: + 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_(); + + InternalGPIOPin *interrupt_pin_{}; + GPIOPin *reset_pin_{}; + uint8_t chip_id_{}; + bool setup_complete_{}; +}; + +} // namespace cst226 +} // namespace esphome diff --git a/esphome/components/cst816/__init__.py b/esphome/components/cst816/__init__.py new file mode 100644 index 0000000000..674a80b7c1 --- /dev/null +++ b/esphome/components/cst816/__init__.py @@ -0,0 +1,6 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@clydebarrow"] +DEPENDENCIES = ["i2c"] + +cst816_ns = cg.esphome_ns.namespace("cst816") diff --git a/esphome/components/cst816/binary_sensor/__init__.py b/esphome/components/cst816/binary_sensor/__init__.py new file mode 100644 index 0000000000..b3fd5bb852 --- /dev/null +++ b/esphome/components/cst816/binary_sensor/__init__.py @@ -0,0 +1,28 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor + +from .. import cst816_ns +from ..touchscreen import CST816Touchscreen, CST816ButtonListener + +CONF_CST816_ID = "cst816_id" + +CST816Button = cst816_ns.class_( + "CST816Button", + binary_sensor.BinarySensor, + cg.Component, + CST816ButtonListener, + cg.Parented.template(CST816Touchscreen), +) + +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(CST816Button).extend( + { + cv.GenerateID(CONF_CST816_ID): cv.use_id(CST816Touchscreen), + } +) + + +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_CST816_ID]) diff --git a/esphome/components/cst816/binary_sensor/cst816_button.h b/esphome/components/cst816/binary_sensor/cst816_button.h new file mode 100644 index 0000000000..4ae856d506 --- /dev/null +++ b/esphome/components/cst816/binary_sensor/cst816_button.h @@ -0,0 +1,27 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/cst816/touchscreen/cst816_touchscreen.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace cst816 { + +class CST816Button : public binary_sensor::BinarySensor, + public Component, + public CST816ButtonListener, + public Parented { + public: + void setup() override { + this->parent_->register_button_listener(this); + this->publish_initial_state(false); + } + + void dump_config() override { LOG_BINARY_SENSOR("", "CST816 Button", this); } + + void update_button(bool state) override { this->publish_state(state); } +}; + +} // namespace cst816 +} // namespace esphome diff --git a/esphome/components/cst816/touchscreen/__init__.py b/esphome/components/cst816/touchscreen/__init__.py new file mode 100644 index 0000000000..a3603ef575 --- /dev/null +++ b/esphome/components/cst816/touchscreen/__init__.py @@ -0,0 +1,34 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome import pins +from esphome.components import i2c, touchscreen +from esphome.const import CONF_INTERRUPT_PIN, CONF_ID, CONF_RESET_PIN +from .. import cst816_ns + + +CST816Touchscreen = cst816_ns.class_( + "CST816Touchscreen", + touchscreen.Touchscreen, + i2c.I2CDevice, +) + +CST816ButtonListener = cst816_ns.class_("CST816ButtonListener") +CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CST816Touchscreen), + cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + } +).extend(i2c.i2c_device_schema(0x15)) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await touchscreen.register_touchscreen(var, config) + await i2c.register_i2c_device(var, config) + + if interrupt_pin := config.get(CONF_INTERRUPT_PIN): + cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) + if reset_pin := config.get(CONF_RESET_PIN): + cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin))) diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp new file mode 100644 index 0000000000..d2b8cc81f1 --- /dev/null +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp @@ -0,0 +1,113 @@ +#include "cst816_touchscreen.h" + +namespace esphome { +namespace cst816 { + +void CST816Touchscreen::continue_setup_() { + if (this->interrupt_pin_ != nullptr) { + this->interrupt_pin_->setup(); + this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); + } + if (!this->read_byte(REG_CHIP_ID, &this->chip_id_)) { + this->mark_failed(); + esph_log_e(TAG, "Failed to read chip id"); + return; + } + switch (this->chip_id_) { + case CST820_CHIP_ID: + case CST716_CHIP_ID: + case CST816S_CHIP_ID: + case CST816D_CHIP_ID: + case CST816T_CHIP_ID: + break; + default: + this->mark_failed(); + esph_log_e(TAG, "Unknown chip ID 0x%02X", this->chip_id_); + return; + } + this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION); + if (this->x_raw_max_ == this->x_raw_min_) { + this->x_raw_max_ = this->display_->get_native_width(); + } + if (this->y_raw_max_ == this->y_raw_min_) { + this->y_raw_max_ = this->display_->get_native_height(); + } + esph_log_config(TAG, "CST816 Touchscreen setup complete"); +} + +void CST816Touchscreen::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); +} + +void CST816Touchscreen::setup() { + esph_log_config(TAG, "Setting up CST816 Touchscreen..."); + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->reset_pin_->digital_write(true); + delay(5); + this->reset_pin_->digital_write(false); + delay(5); + this->reset_pin_->digital_write(true); + this->set_timeout(30, [this] { this->continue_setup_(); }); + } else { + this->continue_setup_(); + } +} + +void CST816Touchscreen::update_touches() { + uint8_t data[13]; + if (!this->read_bytes(REG_STATUS, data, sizeof data)) { + this->status_set_warning(); + return; + } + uint8_t num_of_touches = data[REG_TOUCH_NUM] & 3; + if (num_of_touches == 0) { + this->update_button_state_(false); + return; + } + + uint16_t x = encode_uint16(data[REG_XPOS_HIGH] & 0xF, data[REG_XPOS_LOW]); + uint16_t y = encode_uint16(data[REG_YPOS_HIGH] & 0xF, data[REG_YPOS_LOW]); + esph_log_v(TAG, "Read touch %d/%d", x, y); + if (x >= this->x_raw_max_) { + this->update_button_state_(true); + } else { + this->add_raw_touch_position_(0, x, y); + } +} + +void CST816Touchscreen::dump_config() { + ESP_LOGCONFIG(TAG, "CST816 Touchscreen:"); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + const char *name; + switch (this->chip_id_) { + case CST820_CHIP_ID: + name = "CST820"; + break; + case CST816S_CHIP_ID: + name = "CST816S"; + break; + case CST816D_CHIP_ID: + name = "CST816D"; + break; + case CST716_CHIP_ID: + name = "CST716"; + break; + case CST816T_CHIP_ID: + name = "CST816T"; + break; + default: + name = "Unknown"; + break; + } + ESP_LOGCONFIG(TAG, " Chip type: %s", name); +} + +} // namespace cst816 +} // namespace esphome diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.h b/esphome/components/cst816/touchscreen/cst816_touchscreen.h new file mode 100644 index 0000000000..0d987f2739 --- /dev/null +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.h @@ -0,0 +1,60 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace cst816 { + +static const char *const TAG = "cst816.touchscreen"; + +static const uint8_t REG_STATUS = 0x00; +static const uint8_t REG_TOUCH_NUM = 0x02; +static const uint8_t REG_XPOS_HIGH = 0x03; +static const uint8_t REG_XPOS_LOW = 0x04; +static const uint8_t REG_YPOS_HIGH = 0x05; +static const uint8_t REG_YPOS_LOW = 0x06; +static const uint8_t REG_DIS_AUTOSLEEP = 0xFE; +static const uint8_t REG_CHIP_ID = 0xA7; +static const uint8_t REG_FW_VERSION = 0xA9; +static const uint8_t REG_SLEEP = 0xE5; +static const uint8_t REG_IRQ_CTL = 0xFA; +static const uint8_t IRQ_EN_MOTION = 0x70; + +static const uint8_t CST820_CHIP_ID = 0xB7; +static const uint8_t CST816S_CHIP_ID = 0xB4; +static const uint8_t CST816D_CHIP_ID = 0xB6; +static const uint8_t CST816T_CHIP_ID = 0xB5; +static const uint8_t CST716_CHIP_ID = 0x20; + +class CST816ButtonListener { + public: + virtual void update_button(bool state) = 0; +}; + +class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice { + public: + void setup() override; + void update_touches() override; + void register_button_listener(CST816ButtonListener *listener) { this->button_listeners_.push_back(listener); } + void dump_config() override; + + void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } + void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } + + protected: + void continue_setup_(); + void update_button_state_(bool state); + + InternalGPIOPin *interrupt_pin_{}; + GPIOPin *reset_pin_{}; + uint8_t chip_id_{}; + std::vector button_listeners_; + bool button_touched_{}; +}; + +} // namespace cst816 +} // namespace esphome diff --git a/esphome/components/daikin_arc/__init__.py b/esphome/components/daikin_arc/__init__.py new file mode 100644 index 0000000000..f9164fb02b --- /dev/null +++ b/esphome/components/daikin_arc/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@MagicBear"] diff --git a/esphome/components/daikin_arc/climate.py b/esphome/components/daikin_arc/climate.py new file mode 100644 index 0000000000..51f253e9cb --- /dev/null +++ b/esphome/components/daikin_arc/climate.py @@ -0,0 +1,18 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate_ir +from esphome.const import CONF_ID + +AUTO_LOAD = ["climate_ir"] + +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.extend( + {cv.GenerateID(): cv.declare_id(DaikinArcClimate)} +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await climate_ir.register_climate_ir(var, config) diff --git a/esphome/components/daikin_arc/daikin_arc.cpp b/esphome/components/daikin_arc/daikin_arc.cpp new file mode 100644 index 0000000000..f806463d00 --- /dev/null +++ b/esphome/components/daikin_arc/daikin_arc.cpp @@ -0,0 +1,487 @@ +#include "daikin_arc.h" + +#include + +#include "esphome/components/remote_base/remote_base.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace daikin_arc { + +static const char *const TAG = "daikin.climate"; + +void DaikinArcClimate::setup() { + climate_ir::ClimateIR::setup(); + + // Never send nan to HA + if (std::isnan(this->target_humidity)) + this->target_humidity = 0; + if (std::isnan(this->current_temperature)) + this->current_temperature = 0; + if (std::isnan(this->current_humidity)) + this->current_humidity = 0; +} + +void DaikinArcClimate::transmit_query_() { + uint8_t remote_header[8] = {0x11, 0xDA, 0x27, 0x00, 0x84, 0x87, 0x20, 0x00}; + + // Calculate checksum + for (int i = 0; i < sizeof(remote_header) - 1; i++) { + remote_header[sizeof(remote_header) - 1] += remote_header[i]; + } + + auto transmit = this->transmitter_->transmit(); + auto *data = transmit.get_data(); + data->set_carrier_frequency(DAIKIN_IR_FREQUENCY); + + data->mark(DAIKIN_ARC_PRE_MARK); + data->space(DAIKIN_ARC_PRE_SPACE); + + data->mark(DAIKIN_HEADER_MARK); + data->space(DAIKIN_HEADER_SPACE); + + for (uint8_t i : remote_header) { + for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask + data->mark(DAIKIN_BIT_MARK); + bool bit = i & mask; + data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE); + } + } + data->mark(DAIKIN_BIT_MARK); + data->space(0); + + transmit.perform(); +} + +void DaikinArcClimate::transmit_state() { + // 0x11, 0xDA, 0x27, 0x00, 0xC5, 0x00, 0x00, 0xD7, 0x11, 0xDA, 0x27, 0x00, + // 0x42, 0x49, 0x05, 0xA2, + uint8_t remote_header[20] = {0x11, 0xDA, 0x27, 0x00, 0x02, 0xd0, 0x02, 0x03, 0x80, 0x03, 0x82, 0x30, 0x41, 0x1f, 0x82, + 0xf4, + /* とつど */ + /* 0x13 */ + 0x00, 0x24, 0x00, 0x00}; + + // 05 0 [1:3]MODE 1 [OFF TMR] [ON TMR] Power + // 06-07 TEMP + // 08 [0:3] SPEED [4:7] Swing + // 09 00 + // 10 00 + // 11, 12: timer + // 13 [0:6] 0000000 [7] POWERMODE + // 14 0a + // 15 c4 + // 16 [0:3] 8 00 [6:7] SENSOR WIND = 11 / NORMAL = 00 + // 17 24 + + uint8_t remote_state[19] = { + 0x11, 0xDA, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x0a, 0xC4, + /* MODE TEMP HUMD FANH FANL + パワフル音声応答 */ + /* ON + 0x01入 0x0a */ + /* OF + 0x00切 0x02 */ + 0x80, 0x24, 0x00 + /* センサー風 */ + /* ON 0x83 */ + /* OF 0x80 */ + }; + + remote_state[5] = this->operation_mode_() | 0x08; + remote_state[6] = this->temperature_(); + remote_state[7] = this->humidity_(); + static uint8_t last_humidity = 0x66; + if (remote_state[7] != last_humidity && this->mode != climate::CLIMATE_MODE_OFF) { + ESP_LOGD(TAG, "Set Humditiy: %d, %d\n", (int) this->target_humidity, (int) remote_state[7]); + remote_header[9] |= 0x10; + last_humidity = remote_state[7]; + } + uint16_t fan_speed = this->fan_speed_(); + remote_state[8] = fan_speed >> 8; + remote_state[9] = fan_speed & 0xff; + + // Calculate checksum + for (int i = 0; i < sizeof(remote_header) - 1; i++) { + remote_header[sizeof(remote_header) - 1] += remote_header[i]; + } + + // Calculate checksum + for (int i = 0; i < DAIKIN_STATE_FRAME_SIZE - 1; i++) { + remote_state[DAIKIN_STATE_FRAME_SIZE - 1] += remote_state[i]; + } + + auto transmit = this->transmitter_->transmit(); + auto *data = transmit.get_data(); + data->set_carrier_frequency(DAIKIN_IR_FREQUENCY); + + data->mark(DAIKIN_ARC_PRE_MARK); + data->space(DAIKIN_ARC_PRE_SPACE); + + data->mark(DAIKIN_HEADER_MARK); + data->space(DAIKIN_HEADER_SPACE); + + for (uint8_t i : remote_header) { + for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask + data->mark(DAIKIN_BIT_MARK); + bool bit = i & mask; + data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE); + } + } + data->mark(DAIKIN_BIT_MARK); + data->space(DAIKIN_MESSAGE_SPACE); + + data->mark(DAIKIN_HEADER_MARK); + data->space(DAIKIN_HEADER_SPACE); + + for (uint8_t i : remote_state) { + for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask + data->mark(DAIKIN_BIT_MARK); + bool bit = i & mask; + data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE); + } + } + data->mark(DAIKIN_BIT_MARK); + data->space(0); + + transmit.perform(); +} + +uint8_t DaikinArcClimate::operation_mode_() { + uint8_t operating_mode = DAIKIN_MODE_ON; + switch (this->mode) { + case climate::CLIMATE_MODE_COOL: + operating_mode |= DAIKIN_MODE_COOL; + break; + case climate::CLIMATE_MODE_DRY: + operating_mode |= DAIKIN_MODE_DRY; + break; + case climate::CLIMATE_MODE_HEAT: + operating_mode |= DAIKIN_MODE_HEAT; + break; + case climate::CLIMATE_MODE_HEAT_COOL: + operating_mode |= DAIKIN_MODE_AUTO; + break; + case climate::CLIMATE_MODE_FAN_ONLY: + operating_mode |= DAIKIN_MODE_FAN; + break; + case climate::CLIMATE_MODE_OFF: + default: + operating_mode = DAIKIN_MODE_OFF; + break; + } + + return operating_mode; +} + +uint16_t DaikinArcClimate::fan_speed_() { + uint16_t fan_speed; + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_LOW: + fan_speed = DAIKIN_FAN_1 << 8; + break; + case climate::CLIMATE_FAN_MEDIUM: + fan_speed = DAIKIN_FAN_3 << 8; + break; + case climate::CLIMATE_FAN_HIGH: + fan_speed = DAIKIN_FAN_5 << 8; + break; + case climate::CLIMATE_FAN_AUTO: + default: + fan_speed = DAIKIN_FAN_AUTO << 8; + } + + // If swing is enabled switch first 4 bits to 1111 + switch (this->swing_mode) { + case climate::CLIMATE_SWING_VERTICAL: + fan_speed |= 0x0F00; + break; + case climate::CLIMATE_SWING_HORIZONTAL: + fan_speed |= 0x000F; + break; + case climate::CLIMATE_SWING_BOTH: + fan_speed |= 0x0F0F; + break; + default: + break; + } + return fan_speed; +} + +uint8_t DaikinArcClimate::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: + float new_temp = clamp(this->target_temperature, DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX); + uint8_t temperature = (uint8_t) floor(new_temp); + return temperature << 1 | (new_temp - temperature > 0 ? 0x01 : 0); + } +} + +uint8_t DaikinArcClimate::humidity_() { + if (this->target_humidity == 39) { + return 0; + } else if (this->target_humidity <= 40 || this->target_humidity == 44) { + return 40; + } else if (this->target_humidity <= 45 || this->target_humidity == 49) // 41 - 45 + { + return 45; + } else if (this->target_humidity <= 50 || this->target_humidity == 52) // 45 - 50 + { + return 50; + } else { + return 0xff; + } +} + +climate::ClimateTraits DaikinArcClimate::traits() { + climate::ClimateTraits traits = climate_ir::ClimateIR::traits(); + traits.set_supports_current_temperature(true); + traits.set_supports_current_humidity(false); + traits.set_supports_target_humidity(true); + traits.set_visual_min_humidity(38); + traits.set_visual_max_humidity(52); + return traits; +} + +bool DaikinArcClimate::parse_state_frame_(const uint8_t frame[]) { + uint8_t checksum = 0; + for (int i = 0; i < (DAIKIN_STATE_FRAME_SIZE - 1); i++) { + checksum += frame[i]; + } + if (frame[DAIKIN_STATE_FRAME_SIZE - 1] != checksum) { + ESP_LOGI(TAG, "checksum error"); + return false; + } + + char buf[DAIKIN_STATE_FRAME_SIZE * 3 + 1] = {0}; + for (size_t i = 0; i < DAIKIN_STATE_FRAME_SIZE; i++) { + sprintf(buf, "%s%02x ", buf, frame[i]); + } + ESP_LOGD(TAG, "FRAME %s", buf); + + uint8_t mode = frame[5]; + if (mode & DAIKIN_MODE_ON) { + switch (mode & 0xF0) { + case DAIKIN_MODE_COOL: + this->mode = climate::CLIMATE_MODE_COOL; + break; + case DAIKIN_MODE_DRY: + this->mode = climate::CLIMATE_MODE_DRY; + break; + case DAIKIN_MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + break; + case DAIKIN_MODE_AUTO: + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + break; + case DAIKIN_MODE_FAN: + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + break; + } + } else { + this->mode = climate::CLIMATE_MODE_OFF; + } + uint8_t temperature = frame[6]; + if (!(temperature & 0xC0)) { + this->target_temperature = temperature >> 1; + this->target_temperature += (temperature & 0x1) ? 0.5 : 0; + } + this->target_humidity = frame[7]; // 0, 40, 45, 50, 0xff + uint8_t fan_mode = frame[8]; + uint8_t swing_mode = frame[9]; + if (fan_mode & 0xF && swing_mode & 0xF) { + this->swing_mode = climate::CLIMATE_SWING_BOTH; + } else if (fan_mode & 0xF) { + this->swing_mode = climate::CLIMATE_SWING_VERTICAL; + } else if (swing_mode & 0xF) { + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + } else { + this->swing_mode = climate::CLIMATE_SWING_OFF; + } + 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: + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + break; + case DAIKIN_FAN_4: + case DAIKIN_FAN_5: + this->fan_mode = climate::CLIMATE_FAN_HIGH; + break; + case DAIKIN_FAN_AUTO: + this->fan_mode = climate::CLIMATE_FAN_AUTO; + break; + } + /* + 05 0 [1:3]MODE 1 [OFF TMR] [ON TMR] Power + 06-07 TEMP + 08 [0:3] SPEED [4:7] Swing + 09 00 + 10 00 + 11, 12: timer + 13 [0:6] 0000000 [7] POWERMODE + 14 0a + 15 c4 + 16 [0:3] 8 00 [6:7] SENSOR WIND = 11 / NORMAL = 00 + 17 24 + 05 06 07 08 09 10 11 12 13 14 15 16 17 18 + None FRAME 11 da 27 00 00 49 2e 00 b0 00 00 06 60 00 0a c4 80 24 11 + 1H FRAME 11 da 27 00 00 4d 2e 00 b0 00 00 c6 30 00 2a c4 80 24 c5 + 1H30 FRAME 11 da 27 00 00 4d 2e 00 b0 00 00 a6 32 00 2a c4 80 24 a7 + 2H FRAME 11 da 27 00 00 4d 2e 00 b0 00 00 86 34 00 2a c4 80 24 89 + + */ + this->publish_state(); + return true; +} + +bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) { + uint8_t state_frame[DAIKIN_STATE_FRAME_SIZE] = {}; + + bool valid_daikin_frame = false; + if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) { + valid_daikin_frame = true; + int bytes_count = data.size() / 2 / 8; + std::unique_ptr buf(new char[bytes_count * 3 + 1]); + buf[0] = '\0'; + for (size_t i = 0; i < bytes_count; i++) { + uint8_t byte = 0; + for (int8_t bit = 0; bit < 8; bit++) { + if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE)) { + byte |= 1 << bit; + } else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) { + valid_daikin_frame = false; + break; + } + } + sprintf(buf.get(), "%s%02x ", buf.get(), byte); + } + ESP_LOGD(TAG, "WHOLE FRAME %s size: %d", buf.get(), data.size()); + } + if (!valid_daikin_frame) { + char sbuf[16 * 10 + 1]; + sbuf[0] = '\0'; + for (size_t j = 0; j < data.size(); j++) { + if ((j - 2) % 16 == 0) { + if (j > 0) { + ESP_LOGD(TAG, "DATA %04x: %s", (j - 16 > 0xffff ? 0 : j - 16), sbuf); + } + sbuf[0] = '\0'; + } + char type_ch = ' '; + // debug_tolerance = 25% + + if (DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_MARK)) + type_ch = 'P'; + if (DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_SPACE)) + type_ch = 'a'; + if (DAIKIN_DBG_LOWER(DAIKIN_HEADER_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_HEADER_MARK)) + type_ch = 'H'; + if (DAIKIN_DBG_LOWER(DAIKIN_HEADER_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_HEADER_SPACE)) + type_ch = 'h'; + if (DAIKIN_DBG_LOWER(DAIKIN_BIT_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_BIT_MARK)) + type_ch = 'B'; + if (DAIKIN_DBG_LOWER(DAIKIN_ONE_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ONE_SPACE)) + type_ch = '1'; + if (DAIKIN_DBG_LOWER(DAIKIN_ZERO_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ZERO_SPACE)) + type_ch = '0'; + + if (abs(data[j]) > 100000) { + sprintf(sbuf, "%s%-5d[%c] ", sbuf, data[j] > 0 ? 99999 : -99999, type_ch); + } else { + sprintf(sbuf, "%s%-5d[%c] ", sbuf, (int) (round(data[j] / 10.) * 10), type_ch); + } + if (j == data.size() - 1) { + ESP_LOGD(TAG, "DATA %04x: %s", (j - 8 > 0xffff ? 0 : j - 8), sbuf); + } + } + } + + data.reset(); + + if (!data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) { + ESP_LOGI(TAG, "non daikin_arc expect item"); + return false; + } + + for (uint8_t pos = 0; pos < DAIKIN_STATE_FRAME_SIZE; pos++) { + uint8_t byte = 0; + for (int8_t bit = 0; bit < 8; bit++) { + if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE)) { + byte |= 1 << bit; + } else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) { + ESP_LOGI(TAG, "non daikin_arc expect item pos: %d", pos); + return false; + } + } + state_frame[pos] = byte; + if (pos == 0) { + // frame header + if (byte != 0x11) { + ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte); + return false; + } + } else if (pos == 1) { + // frame header + if (byte != 0xDA) { + ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte); + return false; + } + } else if (pos == 2) { + // frame header + if (byte != 0x27) { + ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte); + return false; + } + } else if (pos == 3) { // NOLINT(bugprone-branch-clone) + // frame header + if (byte != 0x00) { + ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte); + return false; + } + } else if (pos == 4) { + // frame type + if (byte != 0x00) { + ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte); + return false; + } + } else if (pos == 5) { + if (data.size() == 385) { + /* + 11 da 27 00 00 1a 0c 04 2c 21 61 07 00 07 0c 00 18 00 0e 3c 00 6c 1b 61 + Inside Temp + Outside Temp + Humdidity + + */ + this->current_temperature = state_frame[5]; // Inside temperature + // this->current_temperature = state_frame[6]; // Outside temperature + this->publish_state(); + return true; + } else if ((byte & 0x40) != 0x40) { + ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte); + return false; + } + } + } + return this->parse_state_frame_(state_frame); +} + +void DaikinArcClimate::control(const climate::ClimateCall &call) { + if (call.get_target_humidity().has_value()) { + this->target_humidity = *call.get_target_humidity(); + } + climate_ir::ClimateIR::control(call); +} + +} // namespace daikin_arc +} // namespace esphome diff --git a/esphome/components/daikin_arc/daikin_arc.h b/esphome/components/daikin_arc/daikin_arc.h new file mode 100644 index 0000000000..6cfffd4725 --- /dev/null +++ b/esphome/components/daikin_arc/daikin_arc.h @@ -0,0 +1,76 @@ +#pragma once + +#include "esphome/components/climate_ir/climate_ir.h" + +namespace esphome { +namespace daikin_arc { + +// Values for Daikin ARC43XXX IR Controllers +// Temperature +const uint8_t DAIKIN_TEMP_MIN = 10; // Celsius +const uint8_t DAIKIN_TEMP_MAX = 30; // Celsius + +// Modes +const uint8_t DAIKIN_MODE_AUTO = 0x00; +const uint8_t DAIKIN_MODE_COOL = 0x30; +const uint8_t DAIKIN_MODE_HEAT = 0x40; +const uint8_t DAIKIN_MODE_DRY = 0x20; +const uint8_t DAIKIN_MODE_FAN = 0x60; +const uint8_t DAIKIN_MODE_OFF = 0x00; +const uint8_t DAIKIN_MODE_ON = 0x01; + +// Fan Speed +const uint8_t DAIKIN_FAN_AUTO = 0xA0; +const uint8_t DAIKIN_FAN_SILENT = 0xB0; +const uint8_t DAIKIN_FAN_1 = 0x30; +const uint8_t DAIKIN_FAN_2 = 0x40; +const uint8_t DAIKIN_FAN_3 = 0x50; +const uint8_t DAIKIN_FAN_4 = 0x60; +const uint8_t DAIKIN_FAN_5 = 0x70; + +// IR Transmission +const uint32_t DAIKIN_IR_FREQUENCY = 38000; +const uint32_t DAIKIN_ARC_PRE_MARK = 9950; +const uint32_t DAIKIN_ARC_PRE_SPACE = 25100; +const uint32_t DAIKIN_HEADER_MARK = 3450; +const uint32_t DAIKIN_HEADER_SPACE = 1760; +const uint32_t DAIKIN_BIT_MARK = 400; +const uint32_t DAIKIN_ONE_SPACE = 1300; +const uint32_t DAIKIN_ZERO_SPACE = 480; +const uint32_t DAIKIN_MESSAGE_SPACE = 35000; + +const uint8_t DAIKIN_DBG_TOLERANCE = 25; +#define DAIKIN_DBG_LOWER(x) ((100 - DAIKIN_DBG_TOLERANCE) * (x) / 100U) +#define DAIKIN_DBG_UPPER(x) ((100 + DAIKIN_DBG_TOLERANCE) * (x) / 100U) + +// State Frame size +const uint8_t DAIKIN_STATE_FRAME_SIZE = 19; + +class DaikinArcClimate : public climate_ir::ClimateIR { + public: + DaikinArcClimate() + : climate_ir::ClimateIR(DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 0.5f, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, + climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} + + void setup() override; + + protected: + void control(const climate::ClimateCall &call) override; + // Transmit via IR the state of this climate controller. + void transmit_query_(); + void transmit_state() override; + climate::ClimateTraits traits() override; + uint8_t operation_mode_(); + uint16_t fan_speed_(); + uint8_t temperature_(); + uint8_t humidity_(); + // Handle received IR Buffer + bool on_receive(remote_base::RemoteReceiveData data) override; + bool parse_state_frame_(const uint8_t frame[]); +}; + +} // namespace daikin_arc +} // namespace esphome diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 302422d6c7..b1983fef72 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -168,10 +168,6 @@ bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() { if (!wire->reset()) { return false; } - } - - { - InterruptLock lock; wire->select(this->address_); wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD); diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py index e0994be6a0..b1b22b816b 100644 --- a/esphome/components/dashboard_import/__init__.py +++ b/esphome/components/dashboard_import/__init__.py @@ -2,8 +2,10 @@ import base64 import secrets from pathlib import Path from typing import Optional +import re import requests +from ruamel.yaml import YAML import esphome.codegen as cg import esphome.config_validation as cv @@ -11,7 +13,6 @@ import esphome.final_validate as fv from esphome import git from esphome.components.packages import validate_source_shorthand from esphome.const import CONF_REF, CONF_WIFI, CONF_ESPHOME, CONF_PROJECT -from esphome.wizard import wizard_file from esphome.yaml_util import dump dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import") @@ -94,75 +95,74 @@ def import_config( if p.exists(): raise FileExistsError - if project_name == "esphome.web": - if "esp32c3" in import_url: - board = "esp32-c3-devkitm-1" - platform = "ESP32" - elif "esp32s2" in import_url: - board = "esp32-s2-saola-1" - platform = "ESP32" - elif "esp32s3" in import_url: - board = "esp32-s3-devkitc-1" - platform = "ESP32" - elif "esp32" in import_url: - board = "esp32dev" - platform = "ESP32" - elif "esp8266" in import_url: - board = "esp01_1m" - platform = "ESP8266" - elif "pico-w" in import_url: - board = "pico-w" - platform = "RP2040" + git_file = git.GitFile.from_shorthand(import_url) - kwargs = { - "name": name, - "friendly_name": friendly_name, - "platform": platform, - "board": board, - "ssid": "!secret wifi_ssid", - "psk": "!secret wifi_password", + if git_file.query and "full_config" in git_file.query: + url = git_file.raw_url + try: + req = requests.get(url, timeout=30) + req.raise_for_status() + except requests.exceptions.RequestException as e: + raise ValueError(f"Error while fetching {url}: {e}") from e + + contents = req.text + yaml = YAML() + loaded_yaml = yaml.load(contents) + if ( + "name_add_mac_suffix" in loaded_yaml["esphome"] + and loaded_yaml["esphome"]["name_add_mac_suffix"] + ): + loaded_yaml["esphome"]["name_add_mac_suffix"] = False + name_val = loaded_yaml["esphome"]["name"] + sub_pattern = re.compile(r"\$\{?([a-zA-Z-_]+)\}?") + if match := sub_pattern.match(name_val): + name_sub = match.group(1) + if name_sub in loaded_yaml["substitutions"]: + loaded_yaml["substitutions"][name_sub] = name + else: + raise ValueError( + f"Name substitution {name_sub} not found in substitutions" + ) + else: + loaded_yaml["esphome"]["name"] = name + if friendly_name is not None: + friendly_name_val = loaded_yaml["esphome"]["friendly_name"] + if match := sub_pattern.match(friendly_name_val): + friendly_name_sub = match.group(1) + if friendly_name_sub in loaded_yaml["substitutions"]: + loaded_yaml["substitutions"][friendly_name_sub] = friendly_name + else: + raise ValueError( + f"Friendly name substitution {friendly_name_sub} not found in substitutions" + ) + else: + loaded_yaml["esphome"]["friendly_name"] = friendly_name + + with p.open("w", encoding="utf8") as f: + yaml.dump(loaded_yaml, f) + else: + with p.open("w", encoding="utf8") as f: + f.write(contents) + + else: + substitutions = {"name": name} + esphome_core = {"name": "${name}", "name_add_mac_suffix": False} + if friendly_name: + substitutions["friendly_name"] = friendly_name + esphome_core["friendly_name"] = "${friendly_name}" + config = { + "substitutions": substitutions, + "packages": {project_name: import_url}, + "esphome": esphome_core, } if encryption: noise_psk = secrets.token_bytes(32) key = base64.b64encode(noise_psk).decode() - kwargs["api_encryption_key"] = key + config["api"] = {"encryption": {"key": key}} - p.write_text( - wizard_file(**kwargs), - encoding="utf8", - ) - else: - git_file = git.GitFile.from_shorthand(import_url) + output = dump(config) - if git_file.query and "full_config" in git_file.query: - url = git_file.raw_url - try: - req = requests.get(url, timeout=30) - req.raise_for_status() - except requests.exceptions.RequestException as e: - raise ValueError(f"Error while fetching {url}: {e}") from e + if network == CONF_WIFI: + output += WIFI_CONFIG - p.write_text(req.text, encoding="utf8") - - else: - substitutions = {"name": name} - esphome_core = {"name": "${name}", "name_add_mac_suffix": False} - if friendly_name: - substitutions["friendly_name"] = friendly_name - esphome_core["friendly_name"] = "${friendly_name}" - config = { - "substitutions": substitutions, - "packages": {project_name: import_url}, - "esphome": esphome_core, - } - if encryption: - noise_psk = secrets.token_bytes(32) - key = base64.b64encode(noise_psk).decode() - config["api"] = {"encryption": {"key": key}} - - output = dump(config) - - if network == CONF_WIFI: - output += WIFI_CONFIG - - p.write_text(output, encoding="utf8") + p.write_text(output, encoding="utf8") diff --git a/esphome/components/datetime/__init__.py b/esphome/components/datetime/__init__.py new file mode 100644 index 0000000000..3ae99cfff6 --- /dev/null +++ b/esphome/components/datetime/__init__.py @@ -0,0 +1,146 @@ +import esphome.codegen as cg + +# import cpp_generator as cpp +import esphome.config_validation as cv +from esphome import automation +from esphome.components import mqtt +from esphome.const import ( + CONF_ID, + CONF_ON_VALUE, + CONF_TRIGGER_ID, + CONF_TYPE, + CONF_MQTT_ID, + CONF_DATE, + CONF_YEAR, + CONF_MONTH, + CONF_DAY, +) +from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_generator import MockObjClass +from esphome.cpp_helpers import setup_entity + + +CODEOWNERS = ["@rfdarter"] + +IS_PLATFORM_COMPONENT = True + +datetime_ns = cg.esphome_ns.namespace("datetime") +DateTimeBase = datetime_ns.class_("DateTimeBase", cg.EntityBase) +DateEntity = datetime_ns.class_("DateEntity", DateTimeBase) + +# Actions +DateSetAction = datetime_ns.class_("DateSetAction", automation.Action) + +DateTimeStateTrigger = datetime_ns.class_( + "DateTimeStateTrigger", automation.Trigger.template(cg.ESPTime) +) + +DATETIME_MODES = [ + "DATE", + "TIME", + "DATETIME", +] + + +_DATETIME_SCHEMA = cv.Schema( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTDatetimeComponent), + cv.Optional(CONF_ON_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger), + } + ), + } +).extend(cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)) + + +def date_schema(class_: MockObjClass) -> cv.Schema: + schema = { + cv.GenerateID(): cv.declare_id(class_), + cv.Optional(CONF_TYPE, default="DATE"): cv.one_of("DATE", upper=True), + } + return _DATETIME_SCHEMA.extend(schema) + + +def time_schema(class_: MockObjClass) -> cv.Schema: + schema = { + cv.GenerateID(): cv.declare_id(class_), + cv.Optional(CONF_TYPE, default="TIME"): cv.one_of("TIME", upper=True), + } + return _DATETIME_SCHEMA.extend(schema) + + +def datetime_schema(class_: MockObjClass) -> cv.Schema: + schema = { + cv.GenerateID(): cv.declare_id(class_), + cv.Optional(CONF_TYPE, default="DATETIME"): cv.one_of("DATETIME", upper=True), + } + return _DATETIME_SCHEMA.extend(schema) + + +async def setup_datetime_core_(var, config): + await setup_entity(var, config) + + if CONF_MQTT_ID in config: + mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + await mqtt.register_mqtt_component(mqtt_, config) + for conf in config.get(CONF_ON_VALUE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf) + + +async def register_datetime(var, config): + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + cg.add(getattr(cg.App, f"register_{config[CONF_TYPE].lower()}")(var)) + await setup_datetime_core_(var, config) + cg.add_define(f"USE_DATETIME_{config[CONF_TYPE]}") + + +async def new_datetime(config, *args): + var = cg.new_Pvariable(config[CONF_ID], *args) + await register_datetime(var, config) + return var + + +@coroutine_with_priority(40.0) +async def to_code(config): + cg.add_define("USE_DATETIME") + cg.add_global(datetime_ns.using) + + +OPERATION_BASE_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(DateEntity), + } +) + + +@automation.register_action( + "datetime.date.set", + DateSetAction, + OPERATION_BASE_SCHEMA.extend( + { + cv.Required(CONF_DATE): cv.Any( + cv.returning_lambda, cv.date_time(allowed_time=False) + ), + } + ), +) +async def datetime_date_set_to_code(config, action_id, template_arg, args): + action_var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(action_var, config[CONF_ID]) + + date = config[CONF_DATE] + if cg.is_template(date): + template_ = await cg.templatable(config[CONF_DATE], [], cg.ESPTime) + cg.add(action_var.set_date(template_)) + else: + date_struct = cg.StructInitializer( + cg.ESPTime, + ("day_of_month", date[CONF_DAY]), + ("month", date[CONF_MONTH]), + ("year", date[CONF_YEAR]), + ) + cg.add(action_var.set_date(date_struct)) + return action_var diff --git a/esphome/components/datetime/date_entity.cpp b/esphome/components/datetime/date_entity.cpp new file mode 100644 index 0000000000..8b58a8faf7 --- /dev/null +++ b/esphome/components/datetime/date_entity.cpp @@ -0,0 +1,117 @@ +#include "date_entity.h" + +#ifdef USE_DATETIME_DATE + +#include "esphome/core/log.h" + +namespace esphome { +namespace datetime { + +static const char *const TAG = "datetime.date_entity"; + +void DateEntity::publish_state() { + if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) { + this->has_state_ = false; + return; + } + if (this->year_ < 1970 || this->year_ > 3000) { + this->has_state_ = false; + ESP_LOGE(TAG, "Year must be between 1970 and 3000"); + return; + } + if (this->month_ < 1 || this->month_ > 12) { + this->has_state_ = false; + ESP_LOGE(TAG, "Month must be between 1 and 12"); + return; + } + if (this->day_ > days_in_month(this->month_, this->year_)) { + this->has_state_ = false; + ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_); + return; + } + this->has_state_ = true; + ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_); + this->state_callback_.call(); +} + +DateCall DateEntity::make_call() { return DateCall(this); } + +void DateCall::validate_() { + if (this->year_.has_value() && (this->year_ < 1970 || this->year_ > 3000)) { + ESP_LOGE(TAG, "Year must be between 1970 and 3000"); + this->year_.reset(); + } + if (this->month_.has_value() && (this->month_ < 1 || this->month_ > 12)) { + ESP_LOGE(TAG, "Month must be between 1 and 12"); + this->month_.reset(); + } + if (this->day_.has_value()) { + uint16_t year = 0; + uint8_t month = 0; + if (this->month_.has_value()) { + month = *this->month_; + } else { + if (this->parent_->month != 0) { + month = this->parent_->month; + } else { + ESP_LOGE(TAG, "Month must be set to validate day"); + this->day_.reset(); + } + } + if (this->year_.has_value()) { + year = *this->year_; + } else { + if (this->parent_->year != 0) { + year = this->parent_->year; + } else { + ESP_LOGE(TAG, "Year must be set to validate day"); + this->day_.reset(); + } + } + if (this->day_.has_value() && *this->day_ > days_in_month(month, year)) { + ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(month, year), month); + this->day_.reset(); + } + } +} + +void DateCall::perform() { + this->validate_(); + this->parent_->control(*this); +} + +DateCall &DateCall::set_date(uint16_t year, uint8_t month, uint8_t day) { + this->year_ = year; + this->month_ = month; + this->day_ = day; + return *this; +}; + +DateCall &DateCall::set_date(ESPTime time) { return this->set_date(time.year, time.month, time.day_of_month); }; + +DateCall &DateCall::set_date(const std::string &date) { + ESPTime val{}; + if (!ESPTime::strptime(date, val)) { + ESP_LOGE(TAG, "Could not convert the date string to an ESPTime object"); + return *this; + } + return this->set_date(val); +} + +DateCall DateEntityRestoreState::to_call(DateEntity *date) { + DateCall call = date->make_call(); + call.set_date(this->year, this->month, this->day); + return call; +} + +void DateEntityRestoreState::apply(DateEntity *date) { + date->year_ = this->year; + date->month_ = this->month; + date->day_ = this->day; + date->publish_state(); +} + +} // namespace datetime +} // namespace esphome + +#endif // USE_DATETIME_DATE diff --git a/esphome/components/datetime/date_entity.h b/esphome/components/datetime/date_entity.h new file mode 100644 index 0000000000..ce43c5639d --- /dev/null +++ b/esphome/components/datetime/date_entity.h @@ -0,0 +1,117 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_DATETIME_DATE + +#include "esphome/core/automation.h" +#include "esphome/core/helpers.h" +#include "esphome/core/time.h" + +#include "datetime_base.h" + +namespace esphome { +namespace datetime { + +#define LOG_DATETIME_DATE(prefix, type, obj) \ + if ((obj) != nullptr) { \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ + if (!(obj)->get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ + } \ + } + +class DateCall; +class DateEntity; + +struct DateEntityRestoreState { + uint16_t year; + uint8_t month; + uint8_t day; + + DateCall to_call(DateEntity *date); + void apply(DateEntity *date); +} __attribute__((packed)); + +class DateEntity : public DateTimeBase { + protected: + uint16_t year_; + uint8_t month_; + uint8_t day_; + + public: + void publish_state(); + DateCall make_call(); + + ESPTime state_as_esptime() const override { + ESPTime obj; + obj.year = this->year_; + obj.month = this->month_; + obj.day_of_month = this->day_; + return obj; + } + + const uint16_t &year = year_; + const uint8_t &month = month_; + const uint8_t &day = day_; + + protected: + friend class DateCall; + friend struct DateEntityRestoreState; + + virtual void control(const DateCall &call) = 0; +}; + +class DateCall { + public: + explicit DateCall(DateEntity *parent) : parent_(parent) {} + void perform(); + DateCall &set_date(uint16_t year, uint8_t month, uint8_t day); + DateCall &set_date(ESPTime time); + DateCall &set_date(const std::string &date); + + DateCall &set_year(uint16_t year) { + this->year_ = year; + return *this; + } + DateCall &set_month(uint8_t month) { + this->month_ = month; + return *this; + } + DateCall &set_day(uint8_t day) { + this->day_ = day; + return *this; + } + + optional get_year() const { return this->year_; } + optional get_month() const { return this->month_; } + optional get_day() const { return this->day_; } + + protected: + void validate_(); + + DateEntity *parent_; + + optional year_; + optional month_; + optional day_; +}; + +template class DateSetAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(ESPTime, date) + + void play(Ts... x) override { + auto call = this->parent_->make_call(); + + if (this->date_.has_value()) { + call.set_date(this->date_.value(x...)); + } + call.perform(); + } +}; + +} // namespace datetime +} // namespace esphome + +#endif // USE_DATETIME_DATE diff --git a/esphome/components/datetime/datetime_base.h b/esphome/components/datetime/datetime_base.h new file mode 100644 index 0000000000..2f2d27e102 --- /dev/null +++ b/esphome/components/datetime/datetime_base.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/time.h" + +namespace esphome { +namespace datetime { + +class DateTimeBase : public EntityBase { + public: + /// Return whether this Datetime has gotten a full state yet. + bool has_state() const { return this->has_state_; } + + virtual ESPTime state_as_esptime() const = 0; + + void add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } + + protected: + CallbackManager state_callback_; + + bool has_state_{false}; +}; + +class DateTimeStateTrigger : public Trigger { + public: + explicit DateTimeStateTrigger(DateTimeBase *parent) { + parent->add_on_state_callback([this, parent]() { this->trigger(parent->state_as_esptime()); }); + } +}; + +} // namespace datetime +} // namespace esphome diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 90d0313800..157039efc6 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -49,6 +50,8 @@ static uint32_t get_free_heap() { return rp2040.getFreeHeap(); #elif defined(USE_LIBRETINY) return lt_heap_get_free(); +#elif defined(USE_HOST) + return INT_MAX; #endif } diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 328e972e6b..97fdf11366 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -81,7 +81,7 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) { #endif #if defined(USE_ESP32) -#if !defined(USE_ESP32_VARIANT_ESP32C3) +#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; } @@ -121,7 +121,7 @@ void DeepSleepComponent::begin_sleep(bool manual) { App.run_safe_shutdown_hooks(); #if defined(USE_ESP32) -#if !defined(USE_ESP32_VARIANT_ESP32C3) +#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) { @@ -140,7 +140,7 @@ void DeepSleepComponent::begin_sleep(bool manual) { esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); } #endif -#ifdef USE_ESP32_VARIANT_ESP32C3 +#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) { diff --git a/esphome/components/dfplayer/__init__.py b/esphome/components/dfplayer/__init__.py index 5ea04b4804..c37c9999aa 100644 --- a/esphome/components/dfplayer/__init__.py +++ b/esphome/components/dfplayer/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE +from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE, CONF_VOLUME from esphome.components import uart DEPENDENCIES = ["uart"] @@ -19,7 +19,6 @@ DFPlayerIsPlayingCondition = dfplayer_ns.class_( MULTI_CONF = True CONF_FOLDER = "folder" CONF_LOOP = "loop" -CONF_VOLUME = "volume" CONF_EQ_PRESET = "eq_preset" CONF_ON_FINISHED_PLAYBACK = "on_finished_playback" diff --git a/esphome/components/dfplayer/dfplayer.cpp b/esphome/components/dfplayer/dfplayer.cpp index 39a30d035e..aa2dc260e0 100644 --- a/esphome/components/dfplayer/dfplayer.cpp +++ b/esphome/components/dfplayer/dfplayer.cpp @@ -7,10 +7,10 @@ namespace dfplayer { static const char *const TAG = "dfplayer"; void DFPlayer::play_folder(uint16_t folder, uint16_t file) { - if (folder <= 10 && file <= 1000) { + if (folder < 100 && file < 256) { this->ack_set_is_playing_ = true; this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file); - } else if (folder < 100 && file < 256) { + } else if (folder <= 15 && file <= 3000) { this->ack_set_is_playing_ = true; this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file); } else { diff --git a/esphome/components/dfrobot_sen0395/__init__.py b/esphome/components/dfrobot_sen0395/__init__.py index e772db5a15..2197ee5ef8 100644 --- a/esphome/components/dfrobot_sen0395/__init__.py +++ b/esphome/components/dfrobot_sen0395/__init__.py @@ -1,7 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome import core from esphome.automation import maybe_simple_id from esphome.const import CONF_ID from esphome.components import uart @@ -101,7 +100,7 @@ def range_segment_list(input): largest_distance = -1 for distance in input: - if isinstance(distance, core.Lambda): + if isinstance(distance, cv.Lambda): continue m = cv.distance(distance) if m > 9: @@ -128,14 +127,14 @@ MMWAVE_SETTINGS_SCHEMA = cv.Schema( cv.Optional(CONF_OUTPUT_LATENCY): { cv.Required(CONF_DELAY_AFTER_DETECT): cv.templatable( cv.All( - cv.positive_time_period, - cv.Range(max=core.TimePeriod(seconds=1638.375)), + cv.positive_time_period_milliseconds, + cv.Range(max=cv.TimePeriod(seconds=1638.375)), ) ), cv.Required(CONF_DELAY_AFTER_DISAPPEAR): cv.templatable( cv.All( - cv.positive_time_period, - cv.Range(max=core.TimePeriod(seconds=1638.375)), + cv.positive_time_period_milliseconds, + cv.Range(max=cv.TimePeriod(seconds=1638.375)), ) ), }, diff --git a/esphome/components/dfrobot_sen0395/automation.h b/esphome/components/dfrobot_sen0395/automation.h index 1f942c02e4..3f69e482b7 100644 --- a/esphome/components/dfrobot_sen0395/automation.h +++ b/esphome/components/dfrobot_sen0395/automation.h @@ -50,7 +50,7 @@ class DfrobotSen0395SettingsAction : public Action, public Parenteddelay_after_detect_.value(x...); float disappear = this->delay_after_disappear_.value(x...); if (detect >= 0 && disappear >= 0) { - this->parent_->enqueue(make_unique(detect, disappear)); + this->parent_->enqueue(make_unique(detect, disappear)); } } if (this->start_after_power_on_.has_value()) { diff --git a/esphome/components/dfrobot_sen0395/commands.cpp b/esphome/components/dfrobot_sen0395/commands.cpp index 3a89b2b71e..2c60fb3449 100644 --- a/esphome/components/dfrobot_sen0395/commands.cpp +++ b/esphome/components/dfrobot_sen0395/commands.cpp @@ -1,5 +1,7 @@ #include "commands.h" +#include + #include "esphome/core/log.h" #include "dfrobot_sen0395.h" @@ -194,32 +196,22 @@ uint8_t DetRangeCfgCommand::on_message(std::string &message) { return 0; // Command not done yet. } -OutputLatencyCommand::OutputLatencyCommand(float delay_after_detection, float delay_after_disappear) { - delay_after_detection = round(delay_after_detection / 0.025) * 0.025; - delay_after_disappear = round(delay_after_disappear / 0.025) * 0.025; - if (delay_after_detection < 0) - delay_after_detection = 0; - if (delay_after_detection > 1638.375) - delay_after_detection = 1638.375; - if (delay_after_disappear < 0) - delay_after_disappear = 0; - if (delay_after_disappear > 1638.375) - delay_after_disappear = 1638.375; - - this->delay_after_detection_ = delay_after_detection; - this->delay_after_disappear_ = delay_after_disappear; - - this->cmd_ = str_sprintf("outputLatency -1 %.0f %.0f", delay_after_detection / 0.025, delay_after_disappear / 0.025); +SetLatencyCommand::SetLatencyCommand(float delay_after_detection, float delay_after_disappear) { + delay_after_detection = std::round(delay_after_detection / 0.025f) * 0.025f; + delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f; + this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f); + this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f); + this->cmd_ = str_sprintf("setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_); }; -uint8_t OutputLatencyCommand::on_message(std::string &message) { +uint8_t SetLatencyCommand::on_message(std::string &message) { if (message == "sensor is not stopped") { ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!"); return 1; // Command done } else if (message == "Done") { ESP_LOGI(TAG, "Updated output latency config:"); - ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.02fs.", this->delay_after_detection_); - ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.02fs.", this->delay_after_disappear_); + ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.03f s.", this->delay_after_detection_); + ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.03f s.", this->delay_after_disappear_); ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str()); return 1; // Command done } diff --git a/esphome/components/dfrobot_sen0395/commands.h b/esphome/components/dfrobot_sen0395/commands.h index 7426d9732a..cf3ba50be0 100644 --- a/esphome/components/dfrobot_sen0395/commands.h +++ b/esphome/components/dfrobot_sen0395/commands.h @@ -62,9 +62,9 @@ class DetRangeCfgCommand : public Command { // TODO: Set min max values in component, so they can be published as sensor. }; -class OutputLatencyCommand : public Command { +class SetLatencyCommand : public Command { public: - OutputLatencyCommand(float delay_after_detection, float delay_after_disappear); + SetLatencyCommand(float delay_after_detection, float delay_after_disappear); uint8_t on_message(std::string &message) override; protected: diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index c70b227330..5112092073 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -91,7 +91,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r delayMicroseconds(40); } else if (this->model_ == DHT_MODEL_DHT22_TYPE2) { delayMicroseconds(2000); - } else if (this->model_ == DHT_MODEL_AM2302) { + } else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) { delayMicroseconds(1000); } else { delayMicroseconds(800); @@ -217,8 +217,12 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r uint16_t raw_humidity = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF); uint16_t raw_temperature = (uint16_t(data[2] & 0xFF) << 8) | (data[3] & 0xFF); - if (this->model_ != DHT_MODEL_DHT22_TYPE2 && (raw_temperature & 0x8000) != 0) - raw_temperature = ~(raw_temperature & 0x7FFF); + if (raw_temperature & 0x8000) { + if (!(raw_temperature & 0x4000)) + raw_temperature = ~(raw_temperature & 0x7FFF); + } else if (raw_temperature & 0x800) { + raw_temperature |= 0xf000; + } if (raw_temperature == 1 && raw_humidity == 10) { if (report_errors) { diff --git a/esphome/components/dht/dht.h b/esphome/components/dht/dht.h index f3a29f9ce9..327e8a4f5c 100644 --- a/esphome/components/dht/dht.h +++ b/esphome/components/dht/dht.h @@ -11,6 +11,7 @@ enum DHTModel { DHT_MODEL_AUTO_DETECT = 0, DHT_MODEL_DHT11, DHT_MODEL_DHT22, + DHT_MODEL_AM2120, DHT_MODEL_AM2302, DHT_MODEL_RHT03, DHT_MODEL_SI7021, @@ -27,6 +28,7 @@ class DHT : public PollingComponent { * - DHT_MODEL_AUTO_DETECT (default) * - DHT_MODEL_DHT11 * - DHT_MODEL_DHT22 + * - DHT_MODEL_AM2120 * - DHT_MODEL_AM2302 * - DHT_MODEL_RHT03 * - DHT_MODEL_SI7021 diff --git a/esphome/components/dht/sensor.py b/esphome/components/dht/sensor.py index cd1886728e..da92a97e1f 100644 --- a/esphome/components/dht/sensor.py +++ b/esphome/components/dht/sensor.py @@ -23,6 +23,7 @@ DHT_MODELS = { "AUTO_DETECT": DHTModel.DHT_MODEL_AUTO_DETECT, "DHT11": DHTModel.DHT_MODEL_DHT11, "DHT22": DHTModel.DHT_MODEL_DHT22, + "AM2120": DHTModel.DHT_MODEL_AM2120, "AM2302": DHTModel.DHT_MODEL_AM2302, "RHT03": DHTModel.DHT_MODEL_RHT03, "SI7021": DHTModel.DHT_MODEL_SI7021, diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index 9f4e922a37..992799008a 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -18,8 +18,8 @@ from esphome.core import coroutine_with_priority IS_PLATFORM_COMPONENT = True display_ns = cg.esphome_ns.namespace("display") -Display = display_ns.class_("Display") -DisplayBuffer = display_ns.class_("DisplayBuffer") +Display = display_ns.class_("Display", cg.PollingComponent) +DisplayBuffer = display_ns.class_("DisplayBuffer", Display) DisplayPage = display_ns.class_("DisplayPage") DisplayPagePtr = DisplayPage.operator("ptr") DisplayRef = Display.operator("ref") @@ -145,7 +145,7 @@ async def display_page_show_to_code(config, action_id, template_arg, args): DisplayPageShowNextAction, maybe_simple_id( { - cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayBuffer)), + cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)), } ), ) @@ -159,7 +159,7 @@ async def display_page_show_next_to_code(config, action_id, template_arg, args): DisplayPageShowPrevAction, maybe_simple_id( { - cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayBuffer)), + cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)), } ), ) @@ -173,7 +173,7 @@ async def display_page_show_previous_to_code(config, action_id, template_arg, ar DisplayIsDisplayingPageCondition, cv.maybe_simple_value( { - cv.GenerateID(CONF_ID): cv.use_id(DisplayBuffer), + cv.GenerateID(CONF_ID): cv.use_id(Display), cv.Required(CONF_PAGE_ID): cv.use_id(DisplayPage), }, key=CONF_PAGE_ID, diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp index 88ee64ea55..010e6eca0b 100644 --- a/esphome/components/display/display.cpp +++ b/esphome/components/display/display.cpp @@ -35,6 +35,56 @@ void HOT Display::line(int x1, int y1, int x2, int y2, Color color) { } } } + +void Display::line_at_angle(int x, int y, int angle, int length, Color color) { + this->line_at_angle(x, y, angle, 0, length, color); +} + +void Display::line_at_angle(int x, int y, int angle, int start_radius, int stop_radius, Color color) { + // Calculate start and end points + int x1 = (start_radius * cos(angle * M_PI / 180)) + x; + int y1 = (start_radius * sin(angle * M_PI / 180)) + y; + int x2 = (stop_radius * cos(angle * M_PI / 180)) + x; + int y2 = (stop_radius * sin(angle * M_PI / 180)) + y; + + // Draw line + this->line(x1, y1, x2, y2, color); +} + +void Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, + ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { + size_t line_stride = x_offset + w + x_pad; // length of each source line in pixels + uint32_t color_value; + for (int y = 0; y != h; y++) { + size_t source_idx = (y_offset + y) * line_stride + x_offset; + size_t source_idx_mod; + for (int x = 0; x != w; x++, source_idx++) { + switch (bitness) { + default: + color_value = ptr[source_idx]; + break; + case COLOR_BITNESS_565: + source_idx_mod = source_idx * 2; + if (big_endian) { + color_value = (ptr[source_idx_mod] << 8) + ptr[source_idx_mod + 1]; + } else { + color_value = ptr[source_idx_mod] + (ptr[source_idx_mod + 1] << 8); + } + break; + case COLOR_BITNESS_888: + source_idx_mod = source_idx * 3; + if (big_endian) { + color_value = (ptr[source_idx_mod + 0] << 16) + (ptr[source_idx_mod + 1] << 8) + ptr[source_idx_mod + 2]; + } else { + color_value = ptr[source_idx_mod + 0] + (ptr[source_idx_mod + 1] << 8) + (ptr[source_idx_mod + 2] << 16); + } + break; + } + this->draw_pixel_at(x + x_start, y + y_start, ColorUtil::to_color(color_value, order, bitness)); + } + } +} + void HOT Display::horizontal_line(int x, int y, int width, Color color) { // Future: Could be made more efficient by manipulating buffer directly in certain rotations. for (int i = x; i < x + width; i++) @@ -106,18 +156,197 @@ void Display::filled_circle(int center_x, int center_y, int radius, Color color) } } while (dx <= 0); } +void HOT Display::triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) { + this->line(x1, y1, x2, y2, color); + this->line(x1, y1, x3, y3, color); + this->line(x2, y2, x3, y3, color); +} +void Display::sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3) { + if (*y1 > *y2) { + int x_temp = *x1, y_temp = *y1; + *x1 = *x2, *y1 = *y2; + *x2 = x_temp, *y2 = y_temp; + } + if (*y1 > *y3) { + int x_temp = *x1, y_temp = *y1; + *x1 = *x3, *y1 = *y3; + *x3 = x_temp, *y3 = y_temp; + } + if (*y2 > *y3) { + int x_temp = *x2, y_temp = *y2; + *x2 = *x3, *y2 = *y3; + *x3 = x_temp, *y3 = y_temp; + } +} +void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color) { + // y2 must be equal to y3 (same horizontal line) -void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) { + // Initialize Bresenham's algorithm for side 1 + int s1_current_x = x1; + int s1_current_y = y1; + bool s1_axis_swap = false; + int s1_dx = abs(x2 - x1); + int s1_dy = abs(y2 - y1); + int s1_sign_x = ((x2 - x1) >= 0) ? 1 : -1; + int s1_sign_y = ((y2 - y1) >= 0) ? 1 : -1; + if (s1_dy > s1_dx) { // swap values + int tmp = s1_dx; + s1_dx = s1_dy; + s1_dy = tmp; + s1_axis_swap = true; + } + int s1_error = 2 * s1_dy - s1_dx; + + // Initialize Bresenham's algorithm for side 2 + int s2_current_x = x1; + int s2_current_y = y1; + bool s2_axis_swap = false; + int s2_dx = abs(x3 - x1); + int s2_dy = abs(y3 - y1); + int s2_sign_x = ((x3 - x1) >= 0) ? 1 : -1; + int s2_sign_y = ((y3 - y1) >= 0) ? 1 : -1; + if (s2_dy > s2_dx) { // swap values + int tmp = s2_dx; + s2_dx = s2_dy; + s2_dy = tmp; + s2_axis_swap = true; + } + int s2_error = 2 * s2_dy - s2_dx; + + // Iterate on side 1 and allow side 2 to be processed to match the advance of the y-axis. + for (int i = 0; i <= s1_dx; i++) { + if (s1_current_x <= s2_current_x) { + this->horizontal_line(s1_current_x, s1_current_y, s2_current_x - s1_current_x + 1, color); + } else { + this->horizontal_line(s2_current_x, s2_current_y, s1_current_x - s2_current_x + 1, color); + } + + // Bresenham's #1 + // Side 1 s1_current_x and s1_current_y calculation + while (s1_error >= 0) { + if (s1_axis_swap) { + s1_current_x += s1_sign_x; + } else { + s1_current_y += s1_sign_y; + } + s1_error = s1_error - 2 * s1_dx; + } + if (s1_axis_swap) { + s1_current_y += s1_sign_y; + } else { + s1_current_x += s1_sign_x; + } + s1_error = s1_error + 2 * s1_dy; + + // Bresenham's #2 + // Side 2 s2_current_x and s2_current_y calculation + while (s2_current_y != s1_current_y) { + while (s2_error >= 0) { + if (s2_axis_swap) { + s2_current_x += s2_sign_x; + } else { + s2_current_y += s2_sign_y; + } + s2_error = s2_error - 2 * s2_dx; + } + if (s2_axis_swap) { + s2_current_y += s2_sign_y; + } else { + s2_current_x += s2_sign_x; + } + s2_error = s2_error + 2 * s2_dy; + } + } +} +void Display::filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) { + // Sort the three points by y-coordinate ascending, so [x1,y1] is the topmost point + this->sort_triangle_points_by_y_(&x1, &y1, &x2, &y2, &x3, &y3); + + if (y2 == y3) { // Check for special case of a bottom-flat triangle + this->filled_flat_side_triangle_(x1, y1, x2, y2, x3, y3, color); + } else if (y1 == y2) { // Check for special case of a top-flat triangle + this->filled_flat_side_triangle_(x3, y3, x1, y1, x2, y2, color); + } else { // General case: split the no-flat-side triangle in a top-flat triangle and bottom-flat triangle + int x_temp = (int) (x1 + ((float) (y2 - y1) / (float) (y3 - y1)) * (x3 - x1)), y_temp = y2; + this->filled_flat_side_triangle_(x1, y1, x2, y2, x_temp, y_temp, color); + this->filled_flat_side_triangle_(x3, y3, x2, y2, x_temp, y_temp, color); + } +} +void HOT Display::get_regular_polygon_vertex(int vertex_id, int *vertex_x, int *vertex_y, int center_x, int center_y, + int radius, int edges, RegularPolygonVariation variation, + float rotation_degrees) { + if (edges >= 2) { + // Given the orientation of the display component, an angle is measured clockwise from the x axis. + // For a regular polygon, the human reference would be the top of the polygon, + // hence we rotate the shape by 270° to orient the polygon up. + rotation_degrees += ROTATION_270_DEGREES; + // Convert the rotation to radians, easier to use in trigonometrical calculations + float rotation_radians = rotation_degrees * PI / 180; + // A pointy top variation means the first vertex of the polygon is at the top center of the shape, this requires no + // additional rotation of the shape. + // A flat top variation means the first point of the polygon has to be rotated so that the first edge is horizontal, + // this requires to rotate the shape by π/edges radians counter-clockwise so that the first point is located on the + // left side of the first horizontal edge. + rotation_radians -= (variation == VARIATION_FLAT_TOP) ? PI / edges : 0.0; + + float vertex_angle = ((float) vertex_id) / edges * 2 * PI + rotation_radians; + *vertex_x = (int) round(cos(vertex_angle) * radius) + center_x; + *vertex_y = (int) round(sin(vertex_angle) * radius) + center_y; + } +} + +void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, + float rotation_degrees, Color color, RegularPolygonDrawing drawing) { + if (edges >= 2) { + int previous_vertex_x, previous_vertex_y; + for (int current_vertex_id = 0; current_vertex_id <= edges; current_vertex_id++) { + int current_vertex_x, current_vertex_y; + get_regular_polygon_vertex(current_vertex_id, ¤t_vertex_x, ¤t_vertex_y, x, y, radius, edges, + variation, rotation_degrees); + if (current_vertex_id > 0) { // Start drawing after the 2nd vertex coordinates has been calculated + if (drawing == DRAWING_FILLED) { + this->filled_triangle(x, y, previous_vertex_x, previous_vertex_y, current_vertex_x, current_vertex_y, color); + } else if (drawing == DRAWING_OUTLINE) { + this->line(previous_vertex_x, previous_vertex_y, current_vertex_x, current_vertex_y, color); + } + } + previous_vertex_x = current_vertex_x; + previous_vertex_y = current_vertex_y; + } + } +} +void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color, + RegularPolygonDrawing drawing) { + regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, drawing); +} +void HOT Display::regular_polygon(int x, int y, int radius, int edges, Color color, RegularPolygonDrawing drawing) { + regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, drawing); +} +void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, + float rotation_degrees, Color color) { + regular_polygon(x, y, radius, edges, variation, rotation_degrees, color, DRAWING_FILLED); +} +void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, + Color color) { + regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, DRAWING_FILLED); +} +void Display::filled_regular_polygon(int x, int y, int radius, int edges, Color color) { + regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, DRAWING_FILLED); +} + +void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text, Color background) { int x_start, y_start; int width, height; this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height); - font->print(x_start, y_start, this, color, text); + font->print(x_start, y_start, this, color, text, background); } -void Display::vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg) { + +void Display::vprintf_(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, + va_list arg) { char buffer[256]; int ret = vsnprintf(buffer, sizeof(buffer), format, arg); if (ret > 0) - this->print(x, y, font, color, align, buffer); + this->print(x, y, font, color, align, buffer, background); } void Display::image(int x, int y, BaseImage *image, Color color_on, Color color_off) { @@ -211,8 +440,8 @@ void Display::get_text_bounds(int x, int y, const char *text, BaseFont *font, Te break; } } -void Display::print(int x, int y, BaseFont *font, Color color, const char *text) { - this->print(x, y, font, color, TextAlign::TOP_LEFT, text); +void Display::print(int x, int y, BaseFont *font, Color color, const char *text, Color background) { + this->print(x, y, font, color, TextAlign::TOP_LEFT, text, background); } void Display::print(int x, int y, BaseFont *font, TextAlign align, const char *text) { this->print(x, y, font, COLOR_ON, align, text); @@ -220,28 +449,35 @@ void Display::print(int x, int y, BaseFont *font, TextAlign align, const char *t void Display::print(int x, int y, BaseFont *font, const char *text) { this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text); } +void Display::printf(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, + ...) { + va_list arg; + va_start(arg, format); + this->vprintf_(x, y, font, color, background, align, format, arg); + va_end(arg); +} void Display::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) { va_list arg; va_start(arg, format); - this->vprintf_(x, y, font, color, align, format, arg); + this->vprintf_(x, y, font, color, COLOR_OFF, align, format, arg); va_end(arg); } void Display::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) { va_list arg; va_start(arg, format); - this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg); + this->vprintf_(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, arg); va_end(arg); } void Display::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) { va_list arg; va_start(arg, format); - this->vprintf_(x, y, font, COLOR_ON, align, format, arg); + this->vprintf_(x, y, font, COLOR_ON, COLOR_OFF, align, format, arg); va_end(arg); } void Display::printf(int x, int y, BaseFont *font, const char *format, ...) { va_list arg; va_start(arg, format); - this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg); + this->vprintf_(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, arg); va_end(arg); } void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; } diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index 3afcfb9528..a30ba976b4 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -8,6 +8,7 @@ #include "esphome/core/color.h" #include "esphome/core/automation.h" #include "esphome/core/time.h" +#include "display_color_utils.h" #ifdef USE_GRAPH #include "esphome/components/graph/graph.h" @@ -136,6 +137,42 @@ enum DisplayRotation { DISPLAY_ROTATION_270_DEGREES = 270, }; +#define PI 3.1415926535897932384626433832795 + +const int EDGES_TRIGON = 3; +const int EDGES_TRIANGLE = 3; +const int EDGES_TETRAGON = 4; +const int EDGES_QUADRILATERAL = 4; +const int EDGES_PENTAGON = 5; +const int EDGES_HEXAGON = 6; +const int EDGES_HEPTAGON = 7; +const int EDGES_OCTAGON = 8; +const int EDGES_NONAGON = 9; +const int EDGES_ENNEAGON = 9; +const int EDGES_DECAGON = 10; +const int EDGES_HENDECAGON = 11; +const int EDGES_DODECAGON = 12; +const int EDGES_TRIDECAGON = 13; +const int EDGES_TETRADECAGON = 14; +const int EDGES_PENTADECAGON = 15; +const int EDGES_HEXADECAGON = 16; + +const float ROTATION_0_DEGREES = 0.0; +const float ROTATION_45_DEGREES = 45.0; +const float ROTATION_90_DEGREES = 90.0; +const float ROTATION_180_DEGREES = 180.0; +const float ROTATION_270_DEGREES = 270.0; + +enum RegularPolygonVariation { + VARIATION_POINTY_TOP = 0, + VARIATION_FLAT_TOP = 1, +}; + +enum RegularPolygonDrawing { + DRAWING_OUTLINE = 0, + DRAWING_FILLED = 1, +}; + class Display; class DisplayPage; class DisplayOnPageChangeTrigger; @@ -163,7 +200,7 @@ class BaseImage { class BaseFont { public: - virtual void print(int x, int y, Display *display, Color color, const char *text) = 0; + virtual void print(int x, int y, Display *display, Color color, const char *text, Color background) = 0; virtual void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) = 0; }; @@ -174,10 +211,15 @@ class Display : public PollingComponent { /// Clear the entire screen by filling it with OFF pixels. void clear(); - /// Get the width of the image in pixels with rotation applied. - virtual int get_width() = 0; - /// Get the height of the image in pixels with rotation applied. - virtual int get_height() = 0; + /// Get the calculated width of the display in pixels with rotation applied. + virtual int get_width() { return this->get_width_internal(); } + /// Get the calculated height of the display in pixels with rotation applied. + virtual int get_height() { return this->get_height_internal(); } + + /// Get the native (original) width of the display in pixels. + int get_native_width() { return this->get_width_internal(); } + /// Get the native (original) height of the display in pixels. + int get_native_height() { return this->get_height_internal(); } /// Set a single pixel at the specified coordinates to default color. inline void draw_pixel_at(int x, int y) { this->draw_pixel_at(x, y, COLOR_ON); } @@ -185,9 +227,44 @@ class Display : public PollingComponent { /// Set a single pixel at the specified coordinates to the given color. virtual void draw_pixel_at(int x, int y, Color color) = 0; + /** Given an array of pixels encoded in the nominated format, draw these into the display's buffer. + * The naive implementation here will work in all cases, but can be overridden by sub-classes + * in order to optimise the procedure. + * The parameters describe a rectangular block of pixels, potentially within a larger buffer. + * + * \param x_start The starting destination x position + * \param y_start The starting destination y position + * \param w the width of the pixel block + * \param h the height of the pixel block + * \param ptr A pointer to the start of the data to be copied + * \param order The ordering of the colors + * \param bitness Defines the number of bits and their format for each pixel + * \param big_endian True if 16 bit values are stored big-endian + * \param x_offset The initial x-offset into the source buffer. + * \param y_offset The initial y-offset into the source buffer. + * \param x_pad How many pixels are in each line after the end of the pixels to be copied. + * + * The length of each source buffer line (stride) will be x_offset + w + x_pad. + */ + virtual void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, + ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad); + + /// Convenience overload for base case where the pixels are packed into the buffer with no gaps (e.g. suits LVGL.) + void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, + ColorBitness bitness, bool big_endian) { + this->draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, 0, 0, 0); + } + /// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color. void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON); + /// Draw a straight line at the given angle based on the origin [x, y] for a specified length with the given color. + void line_at_angle(int x, int y, int angle, int length, Color color = COLOR_ON); + + /// Draw a straight line at the given angle based on the origin [x, y] from a specified start and stop radius with the + /// given color. + void line_at_angle(int x, int y, int angle, int start_radius, int stop_radius, Color color = COLOR_ON); + /// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color. void horizontal_line(int x, int y, int width, Color color = COLOR_ON); @@ -207,6 +284,48 @@ class Display : public PollingComponent { /// Fill a circle centered around [center_x,center_y] with the radius radius with the given color. void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON); + /// Draw the outline of a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color. + void triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color = COLOR_ON); + + /// Fill a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color. + void filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color = COLOR_ON); + + /// Get the specified vertex (x,y) coordinates for the regular polygon inscribed in the circle centered on + /// [center_x,center_y] with the given radius. Vertex id are 0-indexed and rotate clockwise. In a pointy-topped + /// variation of a polygon with a 0° rotation, the vertex #0 is located at the top of the polygon. In a flat-topped + /// variation of a polygon with a 0° rotation, the vertex #0 is located on the left-side of the horizontal top + /// edge, and the vertex #1 is located on the right-side of the horizontal top edge. + /// Use the edges constants (e.g.: EDGES_HEXAGON) or any integer to specify the number of edges of the polygon. + /// Use the variation to switch between the flat-topped or the pointy-topped variation of the polygon. + /// Use the rotation in degrees to rotate the shape clockwise. + void get_regular_polygon_vertex(int vertex_id, int *vertex_x, int *vertex_y, int center_x, int center_y, int radius, + int edges, RegularPolygonVariation variation = VARIATION_POINTY_TOP, + float rotation_degrees = ROTATION_0_DEGREES); + + /// Draw the outline of a regular polygon inscribed in the circle centered on [x,y] with the given + /// radius and color. + /// Use the edges constants (e.g.: EDGES_HEXAGON) or any integer to specify the number of edges of the polygon. + /// Use the variation to switch between the flat-topped or the pointy-topped variation of the polygon. + /// Use the rotation in degrees to rotate the shape clockwise. + /// Use the drawing to switch between outlining or filling the polygon. + void regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation = VARIATION_POINTY_TOP, + float rotation_degrees = ROTATION_0_DEGREES, Color color = COLOR_ON, + RegularPolygonDrawing drawing = DRAWING_OUTLINE); + void regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color, + RegularPolygonDrawing drawing = DRAWING_OUTLINE); + void regular_polygon(int x, int y, int radius, int edges, Color color, + RegularPolygonDrawing drawing = DRAWING_OUTLINE); + + /// Fill a regular polygon inscribed in the circle centered on [x,y] with the given radius and color. + /// Use the edges constants (e.g.: EDGES_HEXAGON) or any integer to specify the number of edges of the polygon. + /// Use the variation to switch between the flat-topped or the pointy-topped variation of the polygon. + /// Use the rotation in degrees to rotate the shape clockwise. + void filled_regular_polygon(int x, int y, int radius, int edges, + RegularPolygonVariation variation = VARIATION_POINTY_TOP, + float rotation_degrees = ROTATION_0_DEGREES, Color color = COLOR_ON); + void filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color); + void filled_regular_polygon(int x, int y, int radius, int edges, Color color); + /** Print `text` with the anchor point at [x,y] with `font`. * * @param x The x coordinate of the text alignment anchor point. @@ -215,8 +334,10 @@ class Display : public PollingComponent { * @param color The color to draw the text with. * @param align The alignment of the text. * @param text The text to draw. + * @param background When using multi-bit (anti-aliased) fonts, blend this background color into pixels */ - void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text); + void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text, + Color background = COLOR_OFF); /** Print `text` with the top left at [x,y] with `font`. * @@ -225,8 +346,9 @@ class Display : public PollingComponent { * @param font The font to draw the text with. * @param color The color to draw the text with. * @param text The text to draw. + * @param background When using multi-bit (anti-aliased) fonts, blend this background color into pixels */ - void print(int x, int y, BaseFont *font, Color color, const char *text); + void print(int x, int y, BaseFont *font, Color color, const char *text, Color background = COLOR_OFF); /** Print `text` with the anchor point at [x,y] with `font`. * @@ -247,6 +369,20 @@ class Display : public PollingComponent { */ void print(int x, int y, BaseFont *font, const char *text); + /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`. + * + * @param x The x coordinate of the text alignment anchor point. + * @param y The y coordinate of the text alignment anchor point. + * @param font The font to draw the text with. + * @param color The color to draw the text with. + * @param background The background color to use for anti-aliasing + * @param align The alignment of the text. + * @param format The format to use. + * @param ... The arguments to use for the text formatting. + */ + void printf(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, ...) + __attribute__((format(printf, 8, 9))); + /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`. * * @param x The x coordinate of the text alignment anchor point. @@ -498,11 +634,24 @@ class Display : public PollingComponent { protected: bool clamp_x_(int x, int w, int &min_x, int &max_x); bool clamp_y_(int y, int h, int &min_y, int &max_y); - void vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg); + void vprintf_(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, + va_list arg); void do_update_(); void clear_clipping_(); + virtual int get_height_internal() = 0; + virtual int get_width_internal() = 0; + + /** + * This method fills a triangle using only integer variables by using a + * modified bresenham algorithm. + * It is mandatory that [x2,y2] and [x3,y3] lie on the same horizontal line, + * so y2 must be equal to y3. + */ + void filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color); + void sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3); + DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES}; optional writer_{}; DisplayPage *page_{nullptr}; diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 869d97613a..b7c4db56be 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -22,9 +22,6 @@ class DisplayBuffer : public Display { /// Set a single pixel at the specified coordinates to the given color. void draw_pixel_at(int x, int y, Color color) override; - virtual int get_height_internal() = 0; - virtual int get_width_internal() = 0; - protected: virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0; diff --git a/esphome/components/display_menu_base/display_menu_base.cpp b/esphome/components/display_menu_base/display_menu_base.cpp index 0bfee338ca..5502623607 100644 --- a/esphome/components/display_menu_base/display_menu_base.cpp +++ b/esphome/components/display_menu_base/display_menu_base.cpp @@ -60,6 +60,8 @@ void DisplayMenuComponent::left() { if (this->editing_) { this->finish_editing_(); changed = true; + } else { + changed = this->leave_menu_(); } break; case MENU_MODE_JOYSTICK: diff --git a/esphome/components/ektf2232/touchscreen/ektf2232.cpp b/esphome/components/ektf2232/touchscreen/ektf2232.cpp index 00e00bc7e6..ef8f1c6802 100644 --- a/esphome/components/ektf2232/touchscreen/ektf2232.cpp +++ b/esphome/components/ektf2232/touchscreen/ektf2232.cpp @@ -34,24 +34,27 @@ void EKTF2232Touchscreen::setup() { // Get touch resolution uint8_t received[4]; - this->write(GET_X_RES, 4); - if (this->read(received, 4)) { - ESP_LOGE(TAG, "Failed to read X resolution!"); - this->interrupt_pin_->detach_interrupt(); - this->mark_failed(); - return; + if (this->x_raw_max_ == this->x_raw_min_) { + this->write(GET_X_RES, 4); + if (this->read(received, 4)) { + ESP_LOGE(TAG, "Failed to read X resolution!"); + this->interrupt_pin_->detach_interrupt(); + this->mark_failed(); + return; + } + this->x_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4); } - this->x_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4); - this->write(GET_Y_RES, 4); - if (this->read(received, 4)) { - ESP_LOGE(TAG, "Failed to read Y resolution!"); - this->interrupt_pin_->detach_interrupt(); - this->mark_failed(); - return; + if (this->y_raw_max_ == this->y_raw_min_) { + this->write(GET_Y_RES, 4); + if (this->read(received, 4)) { + ESP_LOGE(TAG, "Failed to read Y resolution!"); + this->interrupt_pin_->detach_interrupt(); + this->mark_failed(); + return; + } + this->y_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4); } - this->y_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4); - this->set_power_state(true); } diff --git a/esphome/components/emc2101/__init__.py b/esphome/components/emc2101/__init__.py index 7a7b31cf14..8012d3e897 100644 --- a/esphome/components/emc2101/__init__.py +++ b/esphome/components/emc2101/__init__.py @@ -7,6 +7,8 @@ CODEOWNERS = ["@ellull"] DEPENDENCIES = ["i2c"] +MULTI_CONF = True + CONF_PWM = "pwm" CONF_DIVIDER = "divider" CONF_DAC = "dac" diff --git a/esphome/components/emc2101/sensor/__init__.py b/esphome/components/emc2101/sensor/__init__.py index 03d3d0314e..9f3fbdce00 100644 --- a/esphome/components/emc2101/sensor/__init__.py +++ b/esphome/components/emc2101/sensor/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( + CONF_EXTERNAL_TEMPERATURE, CONF_ID, CONF_SPEED, DEVICE_CLASS_TEMPERATURE, @@ -16,7 +17,6 @@ from .. import EMC2101_COMPONENT_SCHEMA, CONF_EMC2101_ID, emc2101_ns DEPENDENCIES = ["emc2101"] CONF_INTERNAL_TEMPERATURE = "internal_temperature" -CONF_EXTERNAL_TEMPERATURE = "external_temperature" CONF_DUTY_CYCLE = "duty_cycle" EMC2101Sensor = emc2101_ns.class_("EMC2101Sensor", cg.PollingComponent) diff --git a/esphome/components/emmeti/__init__.py b/esphome/components/emmeti/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/emmeti/climate.py b/esphome/components/emmeti/climate.py new file mode 100644 index 0000000000..36585061e6 --- /dev/null +++ b/esphome/components/emmeti/climate.py @@ -0,0 +1,21 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate_ir +from esphome.const import CONF_ID + +CODEOWNERS = ["@E440QF"] +AUTO_LOAD = ["climate_ir"] + +emmeti_ns = cg.esphome_ns.namespace("emmeti") +EmmetiClimate = emmeti_ns.class_("EmmetiClimate", climate_ir.ClimateIR) + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(EmmetiClimate), + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await climate_ir.register_climate_ir(var, config) diff --git a/esphome/components/emmeti/emmeti.cpp b/esphome/components/emmeti/emmeti.cpp new file mode 100644 index 0000000000..3cb184f868 --- /dev/null +++ b/esphome/components/emmeti/emmeti.cpp @@ -0,0 +1,316 @@ +#include "emmeti.h" +#include "esphome/components/remote_base/remote_base.h" + +namespace esphome { +namespace emmeti { + +static const char *const TAG = "emmeti.climate"; + +// setters +uint8_t EmmetiClimate::set_temp_() { + return (uint8_t) roundf(clamp(this->target_temperature, EMMETI_TEMP_MIN, EMMETI_TEMP_MAX) - EMMETI_TEMP_MIN); +} + +uint8_t EmmetiClimate::set_mode_() { + switch (this->mode) { + case climate::CLIMATE_MODE_COOL: + return EMMETI_MODE_COOL; + case climate::CLIMATE_MODE_DRY: + return EMMETI_MODE_DRY; + case climate::CLIMATE_MODE_HEAT: + return EMMETI_MODE_HEAT; + case climate::CLIMATE_MODE_FAN_ONLY: + return EMMETI_MODE_FAN; + case climate::CLIMATE_MODE_HEAT_COOL: + default: + return EMMETI_MODE_HEAT_COOL; + } +} + +uint8_t EmmetiClimate::set_fan_speed_() { + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_LOW: + return EMMETI_FAN_1; + case climate::CLIMATE_FAN_MEDIUM: + return EMMETI_FAN_2; + case climate::CLIMATE_FAN_HIGH: + return EMMETI_FAN_3; + case climate::CLIMATE_FAN_AUTO: + default: + return EMMETI_FAN_AUTO; + } +} + +uint8_t EmmetiClimate::set_blades_() { + if (this->swing_mode == climate::CLIMATE_SWING_VERTICAL) { + switch (this->blades_) { + case EMMETI_BLADES_1: + case EMMETI_BLADES_2: + case EMMETI_BLADES_HIGH: + this->blades_ = EMMETI_BLADES_HIGH; + break; + case EMMETI_BLADES_3: + case EMMETI_BLADES_MID: + this->blades_ = EMMETI_BLADES_MID; + break; + case EMMETI_BLADES_4: + case EMMETI_BLADES_5: + case EMMETI_BLADES_LOW: + this->blades_ = EMMETI_BLADES_LOW; + break; + default: + this->blades_ = EMMETI_BLADES_FULL; + break; + } + } else { + switch (this->blades_) { + case EMMETI_BLADES_1: + case EMMETI_BLADES_2: + case EMMETI_BLADES_HIGH: + this->blades_ = EMMETI_BLADES_1; + break; + case EMMETI_BLADES_3: + case EMMETI_BLADES_MID: + this->blades_ = EMMETI_BLADES_3; + break; + case EMMETI_BLADES_4: + case EMMETI_BLADES_5: + case EMMETI_BLADES_LOW: + this->blades_ = EMMETI_BLADES_5; + break; + default: + this->blades_ = EMMETI_BLADES_STOP; + break; + } + } + return this->blades_; +} + +uint8_t EmmetiClimate::gen_checksum_() { return (this->set_temp_() + this->set_mode_() + 2) % 16; } + +// getters +float EmmetiClimate::get_temp_(uint8_t temp) { return (float) (temp + EMMETI_TEMP_MIN); } + +climate::ClimateMode EmmetiClimate::get_mode_(uint8_t mode) { + switch (mode) { + case EMMETI_MODE_COOL: + return climate::CLIMATE_MODE_COOL; + case EMMETI_MODE_DRY: + return climate::CLIMATE_MODE_DRY; + case EMMETI_MODE_HEAT: + return climate::CLIMATE_MODE_HEAT; + case EMMETI_MODE_HEAT_COOL: + return climate::CLIMATE_MODE_HEAT_COOL; + case EMMETI_MODE_FAN: + return climate::CLIMATE_MODE_FAN_ONLY; + default: + return climate::CLIMATE_MODE_HEAT_COOL; + } +} + +climate::ClimateFanMode EmmetiClimate::get_fan_speed_(uint8_t fan_speed) { + switch (fan_speed) { + case EMMETI_FAN_1: + return climate::CLIMATE_FAN_LOW; + case EMMETI_FAN_2: + return climate::CLIMATE_FAN_MEDIUM; + case EMMETI_FAN_3: + return climate::CLIMATE_FAN_HIGH; + case EMMETI_FAN_AUTO: + default: + return climate::CLIMATE_FAN_AUTO; + } +} + +climate::ClimateSwingMode EmmetiClimate::get_swing_(uint8_t bitmap) { + return (bitmap >> 1) & 0x01 ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; +} + +template T EmmetiClimate::reverse_(T val, size_t len) { + T result = 0; + for (size_t i = 0; i < len; i++) { + result |= ((val & 1 << i) != 0) << (len - 1 - i); + } + return result; +} + +template void EmmetiClimate::add_(T val, size_t len, esphome::remote_base::RemoteTransmitData *data) { + for (size_t i = len; i > 0; i--) { + data->mark(EMMETI_BIT_MARK); + data->space((val & (1 << (i - 1))) ? EMMETI_ONE_SPACE : EMMETI_ZERO_SPACE); + } +} + +template void EmmetiClimate::add_(T val, esphome::remote_base::RemoteTransmitData *data) { + data->mark(EMMETI_BIT_MARK); + data->space((val & 1) ? EMMETI_ONE_SPACE : EMMETI_ZERO_SPACE); +} + +template +void EmmetiClimate::reverse_add_(T val, size_t len, esphome::remote_base::RemoteTransmitData *data) { + this->add_(this->reverse_(val, len), len, data); +} + +bool EmmetiClimate::check_checksum_(uint8_t checksum) { + uint8_t expected = this->gen_checksum_(); + ESP_LOGV(TAG, "Expected checksum: %X", expected); + ESP_LOGV(TAG, "Checksum received: %X", checksum); + + return checksum == expected; +} + +void EmmetiClimate::transmit_state() { + auto transmit = this->transmitter_->transmit(); + auto *data = transmit.get_data(); + data->set_carrier_frequency(EMMETI_IR_FREQUENCY); + + data->mark(EMMETI_HEADER_MARK); + data->space(EMMETI_HEADER_SPACE); + + if (this->mode != climate::CLIMATE_MODE_OFF) { + this->reverse_add_(this->set_mode_(), 3, data); + this->add_(1, data); + this->reverse_add_(this->set_fan_speed_(), 2, data); + this->add_(this->swing_mode != climate::CLIMATE_SWING_OFF, data); + this->add_(0, data); // sleep mode + this->reverse_add_(this->set_temp_(), 4, data); + this->add_(0, 8, data); // zeros + this->add_(0, data); // turbo mode + this->add_(1, data); // light + this->add_(1, data); // tree icon thingy + this->add_(0, data); // blow mode + this->add_(0x52, 11, data); // idk + + data->mark(EMMETI_BIT_MARK); + data->space(EMMETI_MESSAGE_SPACE); + + this->reverse_add_(this->set_blades_(), 4, data); + this->add_(0, 4, data); // zeros + this->reverse_add_(2, 2, data); // thermometer + this->add_(0, 18, data); // zeros + this->reverse_add_(this->gen_checksum_(), 4, data); + } else { + this->add_(9, 12, data); + this->add_(0, 8, data); + this->add_(0x2052, 15, data); + data->mark(EMMETI_BIT_MARK); + data->space(EMMETI_MESSAGE_SPACE); + this->add_(0, 8, data); + this->add_(1, 2, data); + this->add_(0, 18, data); + this->add_(0x0C, 4, data); + } + data->mark(EMMETI_BIT_MARK); + data->space(0); + + transmit.perform(); +} + +bool EmmetiClimate::parse_state_frame_(EmmetiState curr_state) { + this->mode = this->get_mode_(curr_state.mode); + this->fan_mode = this->get_fan_speed_(curr_state.fan_speed); + this->target_temperature = this->get_temp_(curr_state.temp); + this->swing_mode = this->get_swing_(curr_state.bitmap); + // this->blades_ = curr_state.fan_pos; + if (!(curr_state.bitmap & 0x01)) { + this->mode = climate::CLIMATE_MODE_OFF; + } + + this->publish_state(); + return true; +} + +bool EmmetiClimate::on_receive(remote_base::RemoteReceiveData data) { + if (!data.expect_item(EMMETI_HEADER_MARK, EMMETI_HEADER_SPACE)) { + return false; + } + ESP_LOGD(TAG, "Received emmeti frame"); + + EmmetiState curr_state; + + for (size_t pos = 0; pos < 3; pos++) { + if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { + curr_state.mode |= 1 << pos; + } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { + return false; + } + } + + ESP_LOGD(TAG, "Mode: %d", curr_state.mode); + + if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { + curr_state.bitmap |= 1 << 0; + } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { + return false; + } + + ESP_LOGD(TAG, "On: %d", curr_state.bitmap & 0x01); + + for (size_t pos = 0; pos < 2; pos++) { + if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { + curr_state.fan_speed |= 1 << pos; + } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { + return false; + } + } + + ESP_LOGD(TAG, "Fan speed: %d", curr_state.fan_speed); + + for (size_t pos = 0; pos < 2; pos++) { + if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { + curr_state.bitmap |= 1 << (pos + 1); + } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { + return false; + } + } + + ESP_LOGD(TAG, "Swing: %d", (curr_state.bitmap >> 1) & 0x01); + ESP_LOGD(TAG, "Sleep: %d", (curr_state.bitmap >> 2) & 0x01); + + for (size_t pos = 0; pos < 4; pos++) { + if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { + curr_state.temp |= 1 << pos; + } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { + return false; + } + } + + ESP_LOGD(TAG, "Temp: %d", curr_state.temp); + + for (size_t pos = 0; pos < 8; pos++) { + if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { + return false; + } + } + + for (size_t pos = 0; pos < 4; pos++) { + if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { + curr_state.bitmap |= 1 << (pos + 3); + } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { + return false; + } + } + + ESP_LOGD(TAG, "Turbo: %d", (curr_state.bitmap >> 3) & 0x01); + ESP_LOGD(TAG, "Light: %d", (curr_state.bitmap >> 4) & 0x01); + ESP_LOGD(TAG, "Tree: %d", (curr_state.bitmap >> 5) & 0x01); + ESP_LOGD(TAG, "Blow: %d", (curr_state.bitmap >> 6) & 0x01); + + uint16_t control_data = 0; + for (size_t pos = 0; pos < 11; pos++) { + if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { + control_data |= 1 << pos; + } else if (!data.expect_item(EMMETI_BIT_MARK, EMMETI_ZERO_SPACE)) { + return false; + } + } + + if (control_data != 0x250) { + return false; + } + + return this->parse_state_frame_(curr_state); +} + +} // namespace emmeti +} // namespace esphome diff --git a/esphome/components/emmeti/emmeti.h b/esphome/components/emmeti/emmeti.h new file mode 100644 index 0000000000..9bfb7a7a98 --- /dev/null +++ b/esphome/components/emmeti/emmeti.h @@ -0,0 +1,109 @@ +#pragma once + +#include "esphome/components/climate_ir/climate_ir.h" + +namespace esphome { +namespace emmeti { + +const uint8_t EMMETI_TEMP_MIN = 16; // Celsius +const uint8_t EMMETI_TEMP_MAX = 30; // Celsius + +// Modes + +enum EmmetiMode : uint8_t { + EMMETI_MODE_HEAT_COOL = 0x00, + EMMETI_MODE_COOL = 0x01, + EMMETI_MODE_DRY = 0x02, + EMMETI_MODE_FAN = 0x03, + EMMETI_MODE_HEAT = 0x04, +}; + +// Fan Speed + +enum EmmetiFanMode : uint8_t { + EMMETI_FAN_AUTO = 0x00, + EMMETI_FAN_1 = 0x01, + EMMETI_FAN_2 = 0x02, + EMMETI_FAN_3 = 0x03, +}; + +// Fan Position + +enum EmmetiBlades : uint8_t { + EMMETI_BLADES_STOP = 0x00, + EMMETI_BLADES_FULL = 0x01, + EMMETI_BLADES_1 = 0x02, + EMMETI_BLADES_2 = 0x03, + EMMETI_BLADES_3 = 0x04, + EMMETI_BLADES_4 = 0x05, + EMMETI_BLADES_5 = 0x06, + EMMETI_BLADES_LOW = 0x07, + EMMETI_BLADES_MID = 0x09, + EMMETI_BLADES_HIGH = 0x11, +}; + +// IR Transmission +const uint32_t EMMETI_IR_FREQUENCY = 38000; +const uint32_t EMMETI_HEADER_MARK = 9076; +const uint32_t EMMETI_HEADER_SPACE = 4408; +const uint32_t EMMETI_BIT_MARK = 660; +const uint32_t EMMETI_ONE_SPACE = 1630; +const uint32_t EMMETI_ZERO_SPACE = 530; +const uint32_t EMMETI_MESSAGE_SPACE = 20000; + +struct EmmetiState { + uint8_t mode = 0; + uint8_t bitmap = 0; + uint8_t fan_speed = 0; + uint8_t temp = 0; + uint8_t fan_pos = 0; + uint8_t th = 0; + uint8_t checksum = 0; +}; + +class EmmetiClimate : public climate_ir::ClimateIR { + public: + EmmetiClimate() + : climate_ir::ClimateIR(EMMETI_TEMP_MIN, EMMETI_TEMP_MAX, 1.0f, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {} + + protected: + // Transmit via IR the state of this climate controller + void transmit_state() override; + // Handle received IR Buffer + bool on_receive(remote_base::RemoteReceiveData data) override; + bool parse_state_frame_(EmmetiState curr_state); + + // setters + uint8_t set_mode_(); + uint8_t set_temp_(); + uint8_t set_fan_speed_(); + uint8_t gen_checksum_(); + uint8_t set_blades_(); + + // getters + climate::ClimateMode get_mode_(uint8_t mode); + climate::ClimateFanMode get_fan_speed_(uint8_t fan); + void get_blades_(uint8_t fanpos); + // get swing + climate::ClimateSwingMode get_swing_(uint8_t bitmap); + float get_temp_(uint8_t temp); + + // check if the received frame is valid + bool check_checksum_(uint8_t checksum); + + template T reverse_(T val, size_t len); + + template void add_(T val, size_t len, esphome::remote_base::RemoteTransmitData *ata); + + template void add_(T val, esphome::remote_base::RemoteTransmitData *data); + + template void reverse_add_(T val, size_t len, esphome::remote_base::RemoteTransmitData *data); + + uint8_t blades_ = EMMETI_BLADES_STOP; +}; + +} // namespace emmeti +} // namespace esphome diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 5d17633975..50d6d229f9 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -226,7 +226,7 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0) # The default/recommended esp-idf framework version # - https://github.com/espressif/esp-idf/releases # - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf -RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 5) +RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 6) # The platformio/espressif32 version to use for esp-idf frameworks # - https://github.com/platformio/platform-espressif32/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 @@ -271,8 +271,8 @@ def _arduino_check_versions(value): def _esp_idf_check_versions(value): value = value.copy() lookups = { - "dev": (cv.Version(5, 1, 0), "https://github.com/espressif/esp-idf.git"), - "latest": (cv.Version(5, 1, 0), None), + "dev": (cv.Version(5, 1, 2), "https://github.com/espressif/esp-idf.git"), + "latest": (cv.Version(5, 1, 2), None), "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None), } diff --git a/esphome/components/esp32/boards.py b/esphome/components/esp32/boards.py index e6c23c4d96..cd85f3da97 100644 --- a/esphome/components/esp32/boards.py +++ b/esphome/components/esp32/boards.py @@ -42,6 +42,34 @@ ESP32_BASE_PINS = { } ESP32_BOARD_PINS = { + "adafruit_feather_esp32_v2": { + "A0": 26, + "A1": 25, + "A2": 34, + "A3": 39, + "A4": 36, + "A5": 4, + "SCK": 5, + "MOSI": 19, + "MISO": 21, + "RX": 7, + "TX": 8, + "D37": 37, + "LED": 13, + "LED_BUILTIN": 13, + "D12": 12, + "D27": 27, + "D33": 33, + "D15": 15, + "D32": 32, + "D14": 14, + "SCL": 20, + "SDA": 22, + "BUTTON": 38, + "NEOPIXEL": 0, + "PIN_NEOPIXEL": 0, + "NEOPIXEL_POWER": 2, + }, "adafruit_feather_esp32s2_tft": { "BUTTON": 0, "A0": 18, @@ -133,6 +161,10 @@ ESP32_BOARD_PINS = { "BUTTON": 0, "SWITCH": 0, }, + "airm2m_core_esp32c3": { + "LED1_BUILTIN": 12, + "LED2_BUILTIN": 13, + }, "alksesp32": { "A0": 32, "A1": 33, diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index cc6d3d7d4d..ae83715aea 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -45,21 +45,19 @@ void BLEClientBase::loop() { float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { + if (!this->auto_connect_) + return false; if (this->address_ == 0 || device.address_uint64() != this->address_) return false; if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING) return false; - ESP_LOGD(TAG, "[%d] [%s] Found device", this->connection_index_, this->address_str_.c_str()); - this->set_state(espbt::ClientState::DISCOVERED); + this->log_event_("Found device"); + if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG) + esp32_ble_tracker::global_esp32_ble_tracker->print_bt_device_info(device); - auto addr = device.address_uint64(); - this->remote_bda_[0] = (addr >> 40) & 0xFF; - this->remote_bda_[1] = (addr >> 32) & 0xFF; - this->remote_bda_[2] = (addr >> 24) & 0xFF; - this->remote_bda_[3] = (addr >> 16) & 0xFF; - this->remote_bda_[4] = (addr >> 8) & 0xFF; - this->remote_bda_[5] = (addr >> 0) & 0xFF; + this->set_state(espbt::ClientState::DISCOVERED); + this->set_address(device.address_uint64()); this->remote_addr_type_ = device.get_address_type(); return true; } @@ -108,6 +106,10 @@ void BLEClientBase::release_services() { #endif } +void BLEClientBase::log_event_(const char *name) { + ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), name); +} + bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if, esp_ble_gattc_cb_param_t *param) { if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id) @@ -131,51 +133,73 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_OPEN_EVT: { - ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT", this->connection_index_, this->address_str_.c_str()); + if (!this->check_addr(param->open.remote_bda)) + return false; + this->log_event_("ESP_GATTC_OPEN_EVT"); this->conn_id_ = param->open.conn_id; this->service_count_ = 0; if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(), param->open.status); this->set_state(espbt::ClientState::IDLE); - break; + return false; } auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id); if (ret) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_, this->address_str_.c_str(), ret); } + this->set_state(espbt::ClientState::CONNECTED); if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str()); - this->set_state(espbt::ClientState::CONNECTED); + // only set our state, subclients might have more stuff to do yet. this->state_ = espbt::ClientState::ESTABLISHED; break; } esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); break; } - case ESP_GATTC_CFG_MTU_EVT: { - if (param->cfg_mtu.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_, - this->address_str_.c_str(), param->cfg_mtu.mtu, param->cfg_mtu.status); - this->set_state(espbt::ClientState::IDLE); - break; - } - ESP_LOGV(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_.c_str(), - param->cfg_mtu.status, param->cfg_mtu.mtu); - this->mtu_ = param->cfg_mtu.mtu; + case ESP_GATTC_CONNECT_EVT: { + if (!this->check_addr(param->connect.remote_bda)) + return false; + this->log_event_("ESP_GATTC_CONNECT_EVT"); break; } case ESP_GATTC_DISCONNECT_EVT: { - if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0) + if (!this->check_addr(param->disconnect.remote_bda)) return false; - ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_, + ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_, this->address_str_.c_str(), param->disconnect.reason); this->release_services(); this->set_state(espbt::ClientState::IDLE); break; } + + case ESP_GATTC_CFG_MTU_EVT: { + if (this->conn_id_ != param->cfg_mtu.conn_id) + return false; + if (param->cfg_mtu.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_, + this->address_str_.c_str(), param->cfg_mtu.mtu, param->cfg_mtu.status); + // No state change required here - disconnect event will follow if needed. + break; + } + ESP_LOGD(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_.c_str(), + param->cfg_mtu.status, param->cfg_mtu.mtu); + this->mtu_ = param->cfg_mtu.mtu; + break; + } + case ESP_GATTC_CLOSE_EVT: { + if (this->conn_id_ != param->close.conn_id) + return false; + this->log_event_("ESP_GATTC_CLOSE_EVT"); + this->release_services(); + this->set_state(espbt::ClientState::IDLE); + break; + } case ESP_GATTC_SEARCH_RES_EVT: { + if (this->conn_id_ != param->search_res.conn_id) + return false; this->service_count_++; if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { // V3 clients don't need services initialized since @@ -191,7 +215,9 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_SEARCH_CMPL_EVT: { - ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_SEARCH_CMPL_EVT", this->connection_index_, this->address_str_.c_str()); + if (this->conn_id_ != param->search_cmpl.conn_id) + return false; + this->log_event_("ESP_GATTC_SEARCH_CMPL_EVT"); for (auto &svc : this->services_) { ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(), svc->uuid.to_string().c_str()); @@ -199,11 +225,41 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->address_str_.c_str(), svc->start_handle, svc->end_handle); } ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str()); - this->set_state(espbt::ClientState::CONNECTED); this->state_ = espbt::ClientState::ESTABLISHED; break; } + case ESP_GATTC_READ_DESCR_EVT: { + if (this->conn_id_ != param->write.conn_id) + return false; + this->log_event_("ESP_GATTC_READ_DESCR_EVT"); + break; + } + case ESP_GATTC_WRITE_DESCR_EVT: { + if (this->conn_id_ != param->write.conn_id) + return false; + this->log_event_("ESP_GATTC_WRITE_DESCR_EVT"); + break; + } + case ESP_GATTC_WRITE_CHAR_EVT: { + if (this->conn_id_ != param->write.conn_id) + return false; + this->log_event_("ESP_GATTC_WRITE_CHAR_EVT"); + break; + } + case ESP_GATTC_READ_CHAR_EVT: { + if (this->conn_id_ != param->read.conn_id) + return false; + this->log_event_("ESP_GATTC_READ_CHAR_EVT"); + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (this->conn_id_ != param->notify.conn_id) + return false; + this->log_event_("ESP_GATTC_NOTIFY_EVT"); + break; + } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + this->log_event_("ESP_GATTC_REG_FOR_NOTIFY_EVT"); if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE || this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { // Client is responsible for flipping the descriptor value @@ -212,9 +268,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } esp_gattc_descr_elem_t desc_result; uint16_t count = 1; - esp_gatt_status_t descr_status = - esp_ble_gattc_get_descr_by_char_handle(this->gattc_if_, this->connection_index_, param->reg_for_notify.handle, - NOTIFY_DESC_UUID, &desc_result, &count); + esp_gatt_status_t descr_status = esp_ble_gattc_get_descr_by_char_handle( + this->gattc_if_, this->conn_id_, param->reg_for_notify.handle, NOTIFY_DESC_UUID, &desc_result, &count); if (descr_status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_descr_by_char_handle error, status=%d", this->connection_index_, this->address_str_.c_str(), descr_status); @@ -222,7 +277,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } esp_gattc_char_elem_t char_result; esp_gatt_status_t char_status = - esp_ble_gattc_get_all_char(this->gattc_if_, this->connection_index_, param->reg_for_notify.handle, + esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, param->reg_for_notify.handle, param->reg_for_notify.handle, &char_result, &count, 0); if (char_status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_, @@ -238,6 +293,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ esp_err_t status = esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en), (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); + ESP_LOGD(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties); if (status) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, status=%d", this->connection_index_, this->address_str_.c_str(), status); @@ -246,24 +302,31 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } default: + // ideally would check all other events for matching conn_id + ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_.c_str(), event); break; } return true; } +// clients can't call defer() directly since it's protected. +void BLEClientBase::run_later(std::function &&f) { // NOLINT + this->defer(std::move(f)); +} + void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { // This event is sent by the server when it requests security case ESP_GAP_BLE_SEC_REQ_EVT: - if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0) - break; + if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr)) + return; ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event); esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); break; // This event is sent once authentication has completed case ESP_GAP_BLE_AUTH_CMPL_EVT: - if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0) - break; + if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr)) + return; esp_bd_addr_t bd_addr; memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(), @@ -273,11 +336,12 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ param->ble_security.auth_cmpl.fail_reason); } else { this->paired_ = true; - ESP_LOGV(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_, + ESP_LOGD(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_, this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type, param->ble_security.auth_cmpl.auth_mode); } break; + // There are other events we'll want to implement at some point to support things like pass key // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md default: diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index 97886d0b19..fd586e59d6 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -27,6 +27,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { void loop() override; float get_setup_priority() const override; + void run_later(std::function &&f); // NOLINT bool parse_device(const espbt::ESPBTDevice &device) override; void on_scan_end() override {} bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, @@ -39,10 +40,17 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { bool connected() { return this->state_ == espbt::ClientState::ESTABLISHED; } + void set_auto_connect(bool auto_connect) { this->auto_connect_ = auto_connect; } + void set_address(uint64_t address) { this->address_ = address; + this->remote_bda_[0] = (address >> 40) & 0xFF; + this->remote_bda_[1] = (address >> 32) & 0xFF; + this->remote_bda_[2] = (address >> 24) & 0xFF; + this->remote_bda_[3] = (address >> 16) & 0xFF; + this->remote_bda_[4] = (address >> 8) & 0xFF; + this->remote_bda_[5] = (address >> 0) & 0xFF; if (address == 0) { - memset(this->remote_bda_, 0, sizeof(this->remote_bda_)); this->address_str_ = ""; } else { this->address_str_ = @@ -79,20 +87,24 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { virtual void set_connection_type(espbt::ConnectionType ct) { this->connection_type_ = ct; } + bool check_addr(esp_bd_addr_t &addr) { return memcmp(addr, this->remote_bda_, sizeof(esp_bd_addr_t)) == 0; } + protected: int gattc_if_; esp_bd_addr_t remote_bda_; - esp_ble_addr_type_t remote_addr_type_; + esp_ble_addr_type_t remote_addr_type_{BLE_ADDR_TYPE_PUBLIC}; uint16_t conn_id_{0xFFFF}; uint64_t address_{0}; + bool auto_connect_{false}; std::string address_str_{}; uint8_t connection_index_; int16_t service_count_{0}; uint16_t mtu_{23}; bool paired_{false}; espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; - std::vector services_; + + void log_event_(const char *name); }; } // namespace esp32_ble_client diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 2ead59c025..1edeaadbfd 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -1,23 +1,26 @@ import re + import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation +from esphome.components import esp32_ble +from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.const import ( CONF_ACTIVE, + CONF_DURATION, CONF_ID, CONF_INTERVAL, - CONF_DURATION, - CONF_TRIGGER_ID, CONF_MAC_ADDRESS, - CONF_SERVICE_UUID, CONF_MANUFACTURER_ID, CONF_ON_BLE_ADVERTISE, - CONF_ON_BLE_SERVICE_DATA_ADVERTISE, CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE, + CONF_ON_BLE_SERVICE_DATA_ADVERTISE, + CONF_SERVICE_UUID, + CONF_TRIGGER_ID, + KEY_CORE, + KEY_FRAMEWORK_VERSION, ) -from esphome.components import esp32_ble from esphome.core import CORE -from esphome.components.esp32 import add_idf_sdkconfig_option AUTO_LOAD = ["esp32_ble"] DEPENDENCIES = ["esp32"] @@ -263,7 +266,10 @@ async def to_code(config): # https://github.com/espressif/esp-idf/issues/2503 # Match arduino CONFIG_BTU_TASK_STACK_SIZE # https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866 - add_idf_sdkconfig_option("CONFIG_BTU_TASK_STACK_SIZE", 8192) + if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(4, 4, 6): + add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192) + else: + add_idf_sdkconfig_option("CONFIG_BTU_TASK_STACK_SIZE", 8192) add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9) cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 90e69e1cfa..d90eaac3b6 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -141,9 +141,13 @@ void ESP32ImprovComponent::loop() { std::vector urls = {ESPHOME_MY_LINK}; #ifdef USE_WEBSERVER - auto ip = wifi::global_wifi_component->wifi_sta_ip(); - std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); - urls.push_back(webserver_url); + for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) { + if (ip.is_ip4()) { + std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); + urls.push_back(webserver_url); + break; + } + } #endif std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls); this->send_response_(data); @@ -289,7 +293,7 @@ void ESP32ImprovComponent::process_incoming_data_() { this->connecting_sta_ = sta; wifi::global_wifi_component->set_sta(sta); - wifi::global_wifi_component->start_scanning(); + wifi::global_wifi_component->start_connecting(sta, false); this->set_state_(improv::STATE_PROVISIONING); ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), command.password.c_str()); diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.cpp b/esphome/components/esp32_rmt_led_strip/led_strip.cpp index df6ee2ce2f..7727b64f29 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.cpp +++ b/esphome/components/esp32_rmt_led_strip/led_strip.cpp @@ -13,6 +13,8 @@ namespace esp32_rmt_led_strip { static const char *const TAG = "esp32_rmt_led_strip"; +static const uint32_t RMT_CLK_FREQ = 80000000; + static const uint8_t RMT_CLK_DIV = 2; void ESP32RMTLEDStripLightOutput::setup() { @@ -65,7 +67,7 @@ void ESP32RMTLEDStripLightOutput::setup() { void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low) { - float ratio = (float) APB_CLK_FREQ / RMT_CLK_DIV / 1e09f; + float ratio = (float) RMT_CLK_FREQ / RMT_CLK_DIV / 1e09f; // 0-bit this->bit0_.duration0 = (uint32_t) (ratio * bit0_high); @@ -158,11 +160,13 @@ light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index b = 0; break; } - uint8_t multiplier = this->is_rgbw_ ? 4 : 3; - return {this->buf_ + (index * multiplier) + r, - this->buf_ + (index * multiplier) + g, - this->buf_ + (index * multiplier) + b, - this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr, + uint8_t multiplier = this->is_rgbw_ || this->is_wrgb_ ? 4 : 3; + uint8_t white = this->is_wrgb_ ? 0 : 3; + + return {this->buf_ + (index * multiplier) + r + this->is_wrgb_, + this->buf_ + (index * multiplier) + g + this->is_wrgb_, + this->buf_ + (index * multiplier) + b + this->is_wrgb_, + this->is_rgbw_ || this->is_wrgb_ ? this->buf_ + (index * multiplier) + white : nullptr, &this->effect_data_[index], &this->correction_}; } diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.h b/esphome/components/esp32_rmt_led_strip/led_strip.h index 11d61b07e1..51eed8e01c 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.h +++ b/esphome/components/esp32_rmt_led_strip/led_strip.h @@ -33,7 +33,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { int32_t size() const override { return this->num_leds_; } light::LightTraits get_traits() override { auto traits = light::LightTraits(); - if (this->is_rgbw_) { + if (this->is_rgbw_ || this->is_wrgb_) { traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE}); } else { traits.set_supported_color_modes({light::ColorMode::RGB}); @@ -44,6 +44,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { void set_pin(uint8_t pin) { this->pin_ = pin; } void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; } void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; } + void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; } /// Set a maximum refresh rate in µs as some lights do not like being updated too often. void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; } @@ -72,6 +73,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { uint8_t pin_; uint16_t num_leds_; bool is_rgbw_; + bool is_wrgb_; rmt_item32_t bit0_, bit1_; RGBOrder rgb_order_; diff --git a/esphome/components/esp32_rmt_led_strip/light.py b/esphome/components/esp32_rmt_led_strip/light.py index 122ee132a7..d38c7abeb8 100644 --- a/esphome/components/esp32_rmt_led_strip/light.py +++ b/esphome/components/esp32_rmt_led_strip/light.py @@ -52,6 +52,7 @@ CHIPSETS = { CONF_IS_RGBW = "is_rgbw" +CONF_IS_WRGB = "is_wrgb" CONF_BIT0_HIGH = "bit0_high" CONF_BIT0_LOW = "bit0_low" CONF_BIT1_HIGH = "bit1_high" @@ -90,6 +91,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, + cv.Optional(CONF_IS_WRGB, default=False): cv.boolean, cv.Inclusive( CONF_BIT0_HIGH, "custom", @@ -145,6 +147,7 @@ async def to_code(config): cg.add(var.set_rgb_order(config[CONF_RGB_ORDER])) cg.add(var.set_is_rgbw(config[CONF_IS_RGBW])) + cg.add(var.set_is_wrgb(config[CONF_IS_WRGB])) cg.add( var.set_rmt_channel( diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 5336842b9d..00729921a3 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -83,20 +83,22 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: # The default/recommended arduino framework version # - https://github.com/esp8266/Arduino/releases # - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266 -RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 0, 2) +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 1, 2) # The platformio/espressif8266 version to use for arduino 2 framework versions # - https://github.com/platformio/platform-espressif8266/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266 ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 3) # for arduino 3 framework versions ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 2, 0) +# for arduino 4 framework versions +ARDUINO_4_PLATFORM_VERSION = cv.Version(4, 2, 1) def _arduino_check_versions(value): value = value.copy() lookups = { - "dev": (cv.Version(3, 0, 2), "https://github.com/esp8266/Arduino.git"), - "latest": (cv.Version(3, 0, 2), None), + "dev": (cv.Version(3, 1, 2), "https://github.com/esp8266/Arduino.git"), + "latest": (cv.Version(3, 1, 2), None), "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), } @@ -116,7 +118,9 @@ def _arduino_check_versions(value): platform_version = value.get(CONF_PLATFORM_VERSION) if platform_version is None: - if version >= cv.Version(3, 0, 0): + if version >= cv.Version(3, 1, 0): + platform_version = _parse_platform_version(str(ARDUINO_4_PLATFORM_VERSION)) + elif version >= cv.Version(3, 0, 0): platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION)) elif version >= cv.Version(2, 5, 0): platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION)) diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 6f0f3741dd..ade94cb9f5 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -1,6 +1,13 @@ from esphome import pins import esphome.config_validation as cv +import esphome.final_validate as fv import esphome.codegen as cg +from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32C3, + VARIANT_ESP32S2, + VARIANT_ESP32S3, +) from esphome.const import ( CONF_DOMAIN, CONF_ID, @@ -12,9 +19,17 @@ from esphome.const import ( CONF_SUBNET, CONF_DNS1, CONF_DNS2, + CONF_CLK_PIN, + CONF_MISO_PIN, + CONF_MOSI_PIN, + CONF_CS_PIN, + CONF_INTERRUPT_PIN, + CONF_RESET_PIN, + CONF_SPI, ) from esphome.core import CORE, coroutine_with_priority from esphome.components.network import IPAddress +from esphome.components.spi import get_spi_interface, CONF_INTERFACE_INDEX CONFLICTS_WITH = ["wifi"] DEPENDENCIES = ["esp32"] @@ -27,6 +42,8 @@ CONF_MDIO_PIN = "mdio_pin" CONF_CLK_MODE = "clk_mode" CONF_POWER_PIN = "power_pin" +CONF_CLOCK_SPEED = "clock_speed" + EthernetType = ethernet_ns.enum("EthernetType") ETHERNET_TYPES = { "LAN8720": EthernetType.ETHERNET_TYPE_LAN8720, @@ -36,8 +53,11 @@ ETHERNET_TYPES = { "JL1101": EthernetType.ETHERNET_TYPE_JL1101, "KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081, "KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA, + "W5500": EthernetType.ETHERNET_TYPE_W5500, } +SPI_ETHERNET_TYPES = ["W5500"] + emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t") CLK_MODES = { @@ -84,11 +104,22 @@ def _validate(config): return config -CONFIG_SCHEMA = cv.All( +BASE_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(EthernetComponent), + cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA, + cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, + cv.Optional(CONF_USE_ADDRESS): cv.string_strict, + cv.Optional("enable_mdns"): cv.invalid( + "This option has been removed. Please use the [disabled] option under the " + "new mdns component instead." + ), + } +).extend(cv.COMPONENT_SCHEMA) + +RMII_SCHEMA = BASE_SCHEMA.extend( cv.Schema( { - cv.GenerateID(): cv.declare_id(EthernetComponent), - cv.Required(CONF_TYPE): cv.enum(ETHERNET_TYPES, upper=True), cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number, cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_CLK_MODE, default="GPIO0_IN"): cv.enum( @@ -96,19 +127,66 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA, - cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, - cv.Optional(CONF_USE_ADDRESS): cv.string_strict, - cv.Optional("enable_mdns"): cv.invalid( - "This option has been removed. Please use the [disabled] option under the " - "new mdns component instead." + } + ) +) + +SPI_SCHEMA = BASE_SCHEMA.extend( + cv.Schema( + { + cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_MISO_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_number, + cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_CLOCK_SPEED, default="26.67MHz"): cv.All( + cv.frequency, cv.int_range(int(8e6), int(80e6)) ), } - ).extend(cv.COMPONENT_SCHEMA), + ), +) + +CONFIG_SCHEMA = cv.All( + cv.typed_schema( + { + "LAN8720": RMII_SCHEMA, + "RTL8201": RMII_SCHEMA, + "DP83848": RMII_SCHEMA, + "IP101": RMII_SCHEMA, + "JL1101": RMII_SCHEMA, + "KSZ8081": RMII_SCHEMA, + "KSZ8081RNA": RMII_SCHEMA, + "W5500": SPI_SCHEMA, + }, + upper=True, + ), _validate, ) +def _final_validate(config): + if config[CONF_TYPE] not in SPI_ETHERNET_TYPES: + return + if spi_configs := fv.full_config.get().get(CONF_SPI): + variant = get_esp32_variant() + if variant in (VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3): + spi_host = "SPI2_HOST" + else: + spi_host = "SPI3_HOST" + for spi_conf in spi_configs: + if (index := spi_conf.get(CONF_INTERFACE_INDEX)) is not None: + interface = get_spi_interface(index) + if interface == spi_host: + raise cv.Invalid( + f"`spi` component is using interface '{interface}'. " + f"To use {config[CONF_TYPE]}, you must change the `interface` on the `spi` component.", + ) + + +FINAL_VALIDATE_SCHEMA = _final_validate + + def manual_ip(config): return cg.StructInitializer( ManualIP, @@ -125,15 +203,31 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - cg.add(var.set_phy_addr(config[CONF_PHY_ADDR])) - cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) - cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN])) - cg.add(var.set_type(config[CONF_TYPE])) - cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]])) - cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) + if config[CONF_TYPE] == "W5500": + cg.add(var.set_clk_pin(config[CONF_CLK_PIN])) + cg.add(var.set_miso_pin(config[CONF_MISO_PIN])) + cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN])) + cg.add(var.set_cs_pin(config[CONF_CS_PIN])) + if CONF_INTERRUPT_PIN in config: + cg.add(var.set_interrupt_pin(config[CONF_INTERRUPT_PIN])) + if CONF_RESET_PIN in config: + cg.add(var.set_reset_pin(config[CONF_RESET_PIN])) + cg.add(var.set_clock_speed(config[CONF_CLOCK_SPEED])) - if CONF_POWER_PIN in config: - cg.add(var.set_power_pin(config[CONF_POWER_PIN])) + cg.add_define("USE_ETHERNET_SPI") + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True) + add_idf_sdkconfig_option("CONFIG_ETH_SPI_ETHERNET_W5500", True) + else: + cg.add(var.set_phy_addr(config[CONF_PHY_ADDR])) + cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) + cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN])) + cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]])) + if CONF_POWER_PIN in config: + cg.add(var.set_power_pin(config[CONF_POWER_PIN])) + + cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]])) + cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) if CONF_MANUAL_IP in config: cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 50d5855e6a..3c61fbe0a6 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -9,6 +9,11 @@ #include #include "esp_event.h" +#ifdef USE_ETHERNET_SPI +#include +#include +#endif + namespace esphome { namespace ethernet { @@ -33,6 +38,36 @@ void EthernetComponent::setup() { } esp_err_t err; + +#ifdef USE_ETHERNET_SPI + // Install GPIO ISR handler to be able to service SPI Eth modules interrupts + gpio_install_isr_service(0); + + spi_bus_config_t buscfg = { + .mosi_io_num = this->mosi_pin_, + .miso_io_num = this->miso_pin_, + .sclk_io_num = this->clk_pin_, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .data4_io_num = -1, + .data5_io_num = -1, + .data6_io_num = -1, + .data7_io_num = -1, + .max_transfer_sz = 0, + .flags = 0, + .intr_flags = 0, + }; + +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + auto host = SPI2_HOST; +#else + auto host = SPI3_HOST; +#endif + + err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO); + ESPHL_ERROR_CHECK(err, "SPI bus initialize error"); +#endif + err = esp_netif_init(); ESPHL_ERROR_CHECK(err, "ETH netif init error"); err = esp_event_loop_create_default(); @@ -43,10 +78,40 @@ void EthernetComponent::setup() { // Init MAC and PHY configs to default eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + +#ifdef USE_ETHERNET_SPI // Configure SPI interface and Ethernet driver for specific SPI module + spi_device_interface_config_t devcfg = { + .command_bits = 16, // Actually it's the address phase in W5500 SPI frame + .address_bits = 8, // Actually it's the control phase in W5500 SPI frame + .dummy_bits = 0, + .mode = 0, + .duty_cycle_pos = 0, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .clock_speed_hz = this->clock_speed_, + .input_delay_ns = 0, + .spics_io_num = this->cs_pin_, + .flags = 0, + .queue_size = 20, + .pre_cb = nullptr, + .post_cb = nullptr, + }; + + spi_device_handle_t spi_handle = nullptr; + err = spi_bus_add_device(host, &devcfg, &spi_handle); + ESPHL_ERROR_CHECK(err, "SPI bus add device error"); + + eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle); + w5500_config.int_gpio_num = this->interrupt_pin_; + phy_config.phy_addr = this->phy_addr_spi_; + phy_config.reset_gpio_num = this->reset_pin_; + + esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config); +#else phy_config.phy_addr = this->phy_addr_; phy_config.reset_gpio_num = this->power_pin_; - eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); #if ESP_IDF_VERSION_MAJOR >= 5 eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_; @@ -62,9 +127,11 @@ void EthernetComponent::setup() { mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_; esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); +#endif #endif switch (this->type_) { +#if CONFIG_ETH_USE_ESP32_EMAC case ETHERNET_TYPE_LAN8720: { this->phy_ = esp_eth_phy_new_lan87xx(&phy_config); break; @@ -94,6 +161,13 @@ void EthernetComponent::setup() { #endif break; } +#endif +#ifdef USE_ETHERNET_SPI + case ETHERNET_TYPE_W5500: { + this->phy_ = esp_eth_phy_new_w5500(&phy_config); + break; + } +#endif default: { this->mark_failed(); return; @@ -105,10 +179,18 @@ void EthernetComponent::setup() { err = esp_eth_driver_install(ð_config, &this->eth_handle_); ESPHL_ERROR_CHECK(err, "ETH driver install error"); +#ifndef USE_ETHERNET_SPI if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) { // KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide. this->ksz8081_set_clock_reference_(mac); } +#endif + + // use ESP internal eth mac + uint8_t mac_addr[6]; + esp_read_mac(mac_addr, ESP_MAC_ETH); + err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_MAC_ADDR, mac_addr); + ESPHL_ERROR_CHECK(err, "set mac address error"); /* attach Ethernet driver to TCP/IP stack */ err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_)); @@ -119,10 +201,10 @@ void EthernetComponent::setup() { ESPHL_ERROR_CHECK(err, "ETH event handler register error"); err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &EthernetComponent::got_ip_event_handler, nullptr); ESPHL_ERROR_CHECK(err, "GOT IP event handler register error"); -#if ENABLE_IPV6 +#if USE_NETWORK_IPV6 err = esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &EthernetComponent::got_ip6_event_handler, nullptr); - ESPHL_ERROR_CHECK(err, "GOT IP6 event handler register error"); -#endif /* ENABLE_IPV6 */ + ESPHL_ERROR_CHECK(err, "GOT IPv6 event handler register error"); +#endif /* USE_NETWORK_IPV6 */ /* start Ethernet driver state machine */ err = esp_eth_start(this->eth_handle_); @@ -165,20 +247,6 @@ void EthernetComponent::loop() { this->state_ = EthernetComponentState::CONNECTING; this->start_connect_(); } -#if ENABLE_IPV6 - else if (this->got_ipv6_) { - esp_ip6_addr_t ip6_addr; - if (esp_netif_get_ip6_global(this->eth_netif_, &ip6_addr) == 0 && - esp_netif_ip6_get_addr_type(&ip6_addr) == ESP_IP6_ADDR_IS_GLOBAL) { - ESP_LOGCONFIG(TAG, "IPv6 Addr (Global): " IPV6STR, IPV62STR(ip6_addr)); - } else { - esp_netif_get_ip6_linklocal(this->eth_netif_, &ip6_addr); - ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(ip6_addr)); - } - - this->got_ipv6_ = false; - } -#endif /* ENABLE_IPV6 */ break; } } @@ -214,6 +282,10 @@ void EthernetComponent::dump_config() { eth_type = "KSZ8081RNA"; break; + case ETHERNET_TYPE_W5500: + eth_type = "W5500"; + break; + default: eth_type = "Unknown"; break; @@ -221,23 +293,51 @@ void EthernetComponent::dump_config() { ESP_LOGCONFIG(TAG, "Ethernet:"); this->dump_connect_params_(); +#ifdef USE_ETHERNET_SPI + ESP_LOGCONFIG(TAG, " CLK Pin: %u", this->clk_pin_); + ESP_LOGCONFIG(TAG, " MISO Pin: %u", this->miso_pin_); + ESP_LOGCONFIG(TAG, " MOSI Pin: %u", this->mosi_pin_); + ESP_LOGCONFIG(TAG, " CS Pin: %u", this->cs_pin_); + ESP_LOGCONFIG(TAG, " IRQ Pin: %u", this->interrupt_pin_); + ESP_LOGCONFIG(TAG, " Reset Pin: %d", this->reset_pin_); + ESP_LOGCONFIG(TAG, " Clock Speed: %d MHz", this->clock_speed_ / 1000000); +#else if (this->power_pin_ != -1) { ESP_LOGCONFIG(TAG, " Power Pin: %u", this->power_pin_); } ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_); ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_); - ESP_LOGCONFIG(TAG, " Type: %s", eth_type); ESP_LOGCONFIG(TAG, " PHY addr: %u", this->phy_addr_); +#endif + ESP_LOGCONFIG(TAG, " Type: %s", eth_type); } float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } bool EthernetComponent::can_proceed() { return this->is_connected(); } -network::IPAddress EthernetComponent::get_ip_address() { +network::IPAddresses EthernetComponent::get_ip_addresses() { + network::IPAddresses addresses; esp_netif_ip_info_t ip; - esp_netif_get_ip_info(this->eth_netif_, &ip); - return network::IPAddress(&ip.ip); + esp_err_t err = esp_netif_get_ip_info(this->eth_netif_, &ip); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); + // TODO: do something smarter + // return false; + } else { + addresses[0] = network::IPAddress(&ip.ip); + } +#if USE_NETWORK_IPV6 + struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; + uint8_t count = 0; + count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s); + assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES); + for (int i = 0; i < count; i++) { + addresses[i + 1] = network::IPAddress(&if_ip6s[i]); + } +#endif /* USE_NETWORK_IPV6 */ + + return addresses; } void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) { @@ -269,20 +369,33 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { + ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; + const esp_netif_ip_info_t *ip_info = &event->ip_info; + ESP_LOGV(TAG, "[Ethernet event] ETH Got IP " IPSTR, IP2STR(&ip_info->ip)); + global_eth_component->got_ipv4_address_ = true; +#if USE_NETWORK_IPV6 + global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT; +#else global_eth_component->connected_ = true; - ESP_LOGV(TAG, "[Ethernet event] ETH Got IP (num=%" PRId32 ")", event_id); +#endif /* USE_NETWORK_IPV6 */ } -#if ENABLE_IPV6 +#if USE_NETWORK_IPV6 void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { - ESP_LOGV(TAG, "[Ethernet event] ETH Got IP6 (num=%" PRId32 ")", event_id); - global_eth_component->got_ipv6_ = true; + ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data; + ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip)); global_eth_component->ipv6_count_ += 1; + global_eth_component->connected_ = + global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT); } -#endif /* ENABLE_IPV6 */ +#endif /* USE_NETWORK_IPV6 */ void EthernetComponent::start_connect_() { + global_eth_component->got_ipv4_address_ = false; +#if USE_NETWORK_IPV6 + global_eth_component->ipv6_count_ = 0; +#endif /* USE_NETWORK_IPV6 */ this->connect_begin_ = millis(); this->status_set_warning(); @@ -334,12 +447,12 @@ void EthernetComponent::start_connect_() { if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) { ESPHL_ERROR_CHECK(err, "DHCPC start error"); } -#if ENABLE_IPV6 +#if USE_NETWORK_IPV6 err = esp_netif_create_ip6_linklocal(this->eth_netif_); if (err != ESP_OK) { - ESPHL_ERROR_CHECK(err, "IPv6 local failed"); + ESPHL_ERROR_CHECK(err, "Enable IPv6 link local failed"); } -#endif /* ENABLE_IPV6 */ +#endif /* USE_NETWORK_IPV6 */ } this->connect_begin_ = millis(); @@ -362,18 +475,15 @@ void EthernetComponent::dump_connect_params_() { ESP_LOGCONFIG(TAG, " DNS1: %s", network::IPAddress(dns_ip1).str().c_str()); ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2).str().c_str()); -#if ENABLE_IPV6 - if (this->ipv6_count_ > 0) { - esp_ip6_addr_t ip6_addr; - esp_netif_get_ip6_linklocal(this->eth_netif_, &ip6_addr); - ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(ip6_addr)); - - if (esp_netif_get_ip6_global(this->eth_netif_, &ip6_addr) == 0 && - esp_netif_ip6_get_addr_type(&ip6_addr) == ESP_IP6_ADDR_IS_GLOBAL) { - ESP_LOGCONFIG(TAG, "IPv6 Addr (Global): " IPV6STR, IPV62STR(ip6_addr)); - } +#if USE_NETWORK_IPV6 + struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; + uint8_t count = 0; + count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s); + assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES); + for (int i = 0; i < count; i++) { + ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(if_ip6s[i])); } -#endif /* ENABLE_IPV6 */ +#endif /* USE_NETWORK_IPV6 */ esp_err_t err; @@ -393,15 +503,25 @@ void EthernetComponent::dump_connect_params_() { ESP_LOGCONFIG(TAG, " Link Speed: %u", speed == ETH_SPEED_100M ? 100 : 10); } +#ifdef USE_ETHERNET_SPI +void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; } +void EthernetComponent::set_miso_pin(uint8_t miso_pin) { this->miso_pin_ = miso_pin; } +void EthernetComponent::set_mosi_pin(uint8_t mosi_pin) { this->mosi_pin_ = mosi_pin; } +void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; } +void EthernetComponent::set_interrupt_pin(uint8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; } +void EthernetComponent::set_reset_pin(uint8_t reset_pin) { this->reset_pin_ = reset_pin; } +void EthernetComponent::set_clock_speed(int clock_speed) { this->clock_speed_ = clock_speed; } +#else void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; } void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_pin; } void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; } void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; } -void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio) { this->clk_mode_ = clk_mode; this->clk_gpio_ = clk_gpio; } +#endif +void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } std::string EthernetComponent::get_use_address() const { @@ -428,6 +548,7 @@ bool EthernetComponent::powerdown() { return true; } +#ifndef USE_ETHERNET_SPI void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { #define KSZ80XX_PC2R_REG_ADDR (0x1F) @@ -458,6 +579,7 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { #undef KSZ80XX_PC2R_REG_ADDR } +#endif } // namespace ethernet } // namespace esphome diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 11f50af966..621ac87c10 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -23,6 +23,7 @@ enum EthernetType { ETHERNET_TYPE_JL1101, ETHERNET_TYPE_KSZ8081, ETHERNET_TYPE_KSZ8081RNA, + ETHERNET_TYPE_W5500, }; struct ManualIP { @@ -50,15 +51,25 @@ class EthernetComponent : public Component { void on_shutdown() override { powerdown(); } bool is_connected(); +#ifdef USE_ETHERNET_SPI + void set_clk_pin(uint8_t clk_pin); + void set_miso_pin(uint8_t miso_pin); + void set_mosi_pin(uint8_t mosi_pin); + void set_cs_pin(uint8_t cs_pin); + void set_interrupt_pin(uint8_t interrupt_pin); + void set_reset_pin(uint8_t reset_pin); + void set_clock_speed(int clock_speed); +#else void set_phy_addr(uint8_t phy_addr); void set_power_pin(int power_pin); void set_mdc_pin(uint8_t mdc_pin); void set_mdio_pin(uint8_t mdio_pin); - void set_type(EthernetType type); void set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio); +#endif + void set_type(EthernetType type); void set_manual_ip(const ManualIP &manual_ip); - network::IPAddress get_ip_address(); + network::IPAddresses get_ip_addresses(); std::string get_use_address() const; void set_use_address(const std::string &use_address); bool powerdown(); @@ -76,19 +87,30 @@ class EthernetComponent : public Component { void ksz8081_set_clock_reference_(esp_eth_mac_t *mac); std::string use_address_; +#ifdef USE_ETHERNET_SPI + uint8_t clk_pin_; + uint8_t miso_pin_; + uint8_t mosi_pin_; + uint8_t cs_pin_; + uint8_t interrupt_pin_; + int reset_pin_{-1}; + int phy_addr_spi_{-1}; + int clock_speed_; +#else uint8_t phy_addr_{0}; int power_pin_{-1}; uint8_t mdc_pin_{23}; uint8_t mdio_pin_{18}; - EthernetType type_{ETHERNET_TYPE_UNKNOWN}; emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; +#endif + EthernetType type_{ETHERNET_TYPE_UNKNOWN}; optional manual_ip_{}; bool started_{false}; bool connected_{false}; + bool got_ipv4_address_{false}; #if LWIP_IPV6 - bool got_ipv6_{false}; uint8_t ipv6_count_{0}; #endif /* LWIP_IPV6 */ EthernetComponentState state_{EthernetComponentState::STOPPED}; diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.h b/esphome/components/ethernet_info/ethernet_info_text_sensor.h index 2d46fe18eb..b5764d2519 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.h +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.h @@ -12,19 +12,30 @@ namespace ethernet_info { class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor { public: void update() override { - auto ip = ethernet::global_eth_component->get_ip_address(); - if (ip != this->last_ip_) { - this->last_ip_ = ip; - this->publish_state(network::IPAddress(ip).str()); + auto ips = ethernet::global_eth_component->get_ip_addresses(); + if (ips != this->last_ips_) { + this->last_ips_ = ips; + this->publish_state(ips[0].str()); + uint8_t sensor = 0; + for (auto &ip : ips) { + if (ip.is_set()) { + if (this->ip_sensors_[sensor] != nullptr) { + this->ip_sensors_[sensor]->publish_state(ip.str()); + } + sensor++; + } + } } } float get_setup_priority() const override { return setup_priority::ETHERNET; } std::string unique_id() override { return get_mac_address() + "-ethernetinfo"; } void dump_config() override; + void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; } protected: - network::IPAddress last_ip_; + network::IPAddresses last_ips_; + std::array ip_sensors_; }; } // namespace ethernet_info diff --git a/esphome/components/ethernet_info/text_sensor.py b/esphome/components/ethernet_info/text_sensor.py index 7cb9944c92..b802c427e8 100644 --- a/esphome/components/ethernet_info/text_sensor.py +++ b/esphome/components/ethernet_info/text_sensor.py @@ -18,17 +18,25 @@ CONFIG_SCHEMA = cv.Schema( { cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( IPAddressEsthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ).extend(cv.polling_component_schema("1s")) + ) + .extend(cv.polling_component_schema("1s")) + .extend( + { + cv.Optional(f"address_{x}"): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ) + for x in range(5) + } + ) } ) -async def setup_conf(config, key): - if key in config: - conf = config[key] - var = await text_sensor.new_text_sensor(conf) - await cg.register_component(var, conf) - - async def to_code(config): - await setup_conf(config, CONF_IP_ADDRESS) + if conf := config.get(CONF_IP_ADDRESS): + ip_info = await text_sensor.new_text_sensor(config[CONF_IP_ADDRESS]) + await cg.register_component(ip_info, config[CONF_IP_ADDRESS]) + for x in range(5): + if sensor_conf := conf.get(f"address_{x}"): + sens = await text_sensor.new_text_sensor(sensor_conf) + cg.add(ip_info.add_ip_sensors(x, sens)) diff --git a/esphome/components/ezo_pmp/__init__.py b/esphome/components/ezo_pmp/__init__.py index e65fcf74ca..87cda41f89 100644 --- a/esphome/components/ezo_pmp/__init__.py +++ b/esphome/components/ezo_pmp/__init__.py @@ -1,7 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c -from esphome.const import CONF_ADDRESS, CONF_COMMAND, CONF_ID, CONF_DURATION +from esphome.const import ( + CONF_ADDRESS, + CONF_COMMAND, + CONF_ID, + CONF_DURATION, + CONF_VOLUME, +) from esphome import automation from esphome.automation import maybe_simple_id @@ -9,7 +15,6 @@ CODEOWNERS = ["@carlos-sarmiento"] DEPENDENCIES = ["i2c"] MULTI_CONF = True -CONF_VOLUME = "volume" CONF_VOLUME_PER_MINUTE = "volume_per_minute" ezo_pmp_ns = cg.esphome_ns.namespace("ezo_pmp") diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index fd0f2f66cb..05e276d987 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -15,7 +15,10 @@ from esphome.const import ( CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_OFF_SPEED_CYCLE, + CONF_ON_DIRECTION_SET, + CONF_ON_OSCILLATING_SET, CONF_ON_SPEED_SET, + CONF_ON_STATE, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_ON_PRESET_SET, @@ -55,11 +58,22 @@ TurnOffAction = fan_ns.class_("TurnOffAction", automation.Action) ToggleAction = fan_ns.class_("ToggleAction", automation.Action) CycleSpeedAction = fan_ns.class_("CycleSpeedAction", automation.Action) +FanStateTrigger = fan_ns.class_( + "FanStateTrigger", automation.Trigger.template(Fan.operator("ptr")) +) FanTurnOnTrigger = fan_ns.class_("FanTurnOnTrigger", automation.Trigger.template()) FanTurnOffTrigger = fan_ns.class_("FanTurnOffTrigger", automation.Trigger.template()) -FanSpeedSetTrigger = fan_ns.class_("FanSpeedSetTrigger", automation.Trigger.template()) +FanDirectionSetTrigger = fan_ns.class_( + "FanDirectionSetTrigger", automation.Trigger.template(FanDirection) +) +FanOscillatingSetTrigger = fan_ns.class_( + "FanOscillatingSetTrigger", automation.Trigger.template(cg.bool_) +) +FanSpeedSetTrigger = fan_ns.class_( + "FanSpeedSetTrigger", automation.Trigger.template(cg.int_) +) FanPresetSetTrigger = fan_ns.class_( - "FanPresetSetTrigger", automation.Trigger.template() + "FanPresetSetTrigger", automation.Trigger.template(cg.std_string) ) FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template()) @@ -90,6 +104,11 @@ FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exte cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All( cv.requires_component("mqtt"), cv.subscribe_topic ), + cv.Optional(CONF_ON_STATE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanStateTrigger), + } + ), cv.Optional(CONF_ON_TURN_ON): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger), @@ -100,6 +119,16 @@ FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exte cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger), } ), + cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanDirectionSetTrigger), + } + ), + cv.Optional(CONF_ON_OSCILLATING_SET): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanOscillatingSetTrigger), + } + ), cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger), @@ -186,18 +215,27 @@ async def setup_fan_core_(var, config): mqtt_.set_custom_speed_command_topic(config[CONF_SPEED_COMMAND_TOPIC]) ) + for conf in config.get(CONF_ON_STATE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(Fan.operator("ptr"), "x")], conf) for conf in config.get(CONF_ON_TURN_ON, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) for conf in config.get(CONF_ON_TURN_OFF, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_DIRECTION_SET, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(FanDirection, "x")], conf) + for conf in config.get(CONF_ON_OSCILLATING_SET, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.bool_, "x")], conf) for conf in config.get(CONF_ON_SPEED_SET, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_automation(trigger, [(cg.int_, "x")], conf) for conf in config.get(CONF_ON_PRESET_SET, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_automation(trigger, [(cg.std_string, "x")], conf) async def register_fan(var, config): diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index b5bdeb8a29..d480a2ef44 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -111,6 +111,13 @@ template class FanIsOffCondition : public Condition { Fan *state_; }; +class FanStateTrigger : public Trigger { + public: + FanStateTrigger(Fan *state) { + state->add_on_state_callback([this, state]() { this->trigger(state); }); + } +}; + class FanTurnOnTrigger : public Trigger<> { public: FanTurnOnTrigger(Fan *state) { @@ -147,15 +154,51 @@ class FanTurnOffTrigger : public Trigger<> { bool last_on_; }; -class FanSpeedSetTrigger : public Trigger<> { +class FanDirectionSetTrigger : public Trigger { + public: + FanDirectionSetTrigger(Fan *state) { + state->add_on_state_callback([this, state]() { + auto direction = state->direction; + auto should_trigger = direction != this->last_direction_; + this->last_direction_ = direction; + if (should_trigger) { + this->trigger(direction); + } + }); + this->last_direction_ = state->direction; + } + + protected: + FanDirection last_direction_; +}; + +class FanOscillatingSetTrigger : public Trigger { + public: + FanOscillatingSetTrigger(Fan *state) { + state->add_on_state_callback([this, state]() { + auto oscillating = state->oscillating; + auto should_trigger = oscillating != this->last_oscillating_; + this->last_oscillating_ = oscillating; + if (should_trigger) { + this->trigger(oscillating); + } + }); + this->last_oscillating_ = state->oscillating; + } + + protected: + bool last_oscillating_; +}; + +class FanSpeedSetTrigger : public Trigger { public: FanSpeedSetTrigger(Fan *state) { state->add_on_state_callback([this, state]() { auto speed = state->speed; - auto should_trigger = speed != !this->last_speed_; + auto should_trigger = speed != this->last_speed_; this->last_speed_ = speed; if (should_trigger) { - this->trigger(); + this->trigger(speed); } }); this->last_speed_ = state->speed; @@ -165,7 +208,7 @@ class FanSpeedSetTrigger : public Trigger<> { int last_speed_; }; -class FanPresetSetTrigger : public Trigger<> { +class FanPresetSetTrigger : public Trigger { public: FanPresetSetTrigger(Fan *state) { state->add_on_state_callback([this, state]() { @@ -173,7 +216,7 @@ class FanPresetSetTrigger : public Trigger<> { auto should_trigger = preset_mode != this->last_preset_mode_; this->last_preset_mode_ = preset_mode; if (should_trigger) { - this->trigger(); + this->trigger(preset_mode); } }); this->last_preset_mode_ = state->preset_mode; diff --git a/esphome/components/fingerprint_grow/__init__.py b/esphome/components/fingerprint_grow/__init__.py index ecbbc3d477..23651bd049 100644 --- a/esphome/components/fingerprint_grow/__init__.py +++ b/esphome/components/fingerprint_grow/__init__.py @@ -13,8 +13,11 @@ from esphome.const import ( CONF_ON_ENROLLMENT_DONE, CONF_ON_ENROLLMENT_FAILED, CONF_ON_ENROLLMENT_SCAN, + CONF_ON_FINGER_SCAN_START, CONF_ON_FINGER_SCAN_MATCHED, CONF_ON_FINGER_SCAN_UNMATCHED, + CONF_ON_FINGER_SCAN_MISPLACED, + CONF_ON_FINGER_SCAN_INVALID, CONF_PASSWORD, CONF_SENSING_PIN, CONF_SPEED, @@ -22,18 +25,24 @@ from esphome.const import ( CONF_TRIGGER_ID, ) -CODEOWNERS = ["@OnFreund", "@loongyh"] +CODEOWNERS = ["@OnFreund", "@loongyh", "@alexborro"] DEPENDENCIES = ["uart"] AUTO_LOAD = ["binary_sensor", "sensor"] MULTI_CONF = True CONF_FINGERPRINT_GROW_ID = "fingerprint_grow_id" +CONF_SENSOR_POWER_PIN = "sensor_power_pin" +CONF_IDLE_PERIOD_TO_SLEEP = "idle_period_to_sleep" fingerprint_grow_ns = cg.esphome_ns.namespace("fingerprint_grow") FingerprintGrowComponent = fingerprint_grow_ns.class_( "FingerprintGrowComponent", cg.PollingComponent, uart.UARTDevice ) +FingerScanStartTrigger = fingerprint_grow_ns.class_( + "FingerScanStartTrigger", automation.Trigger.template() +) + FingerScanMatchedTrigger = fingerprint_grow_ns.class_( "FingerScanMatchedTrigger", automation.Trigger.template(cg.uint16, cg.uint16) ) @@ -42,6 +51,14 @@ FingerScanUnmatchedTrigger = fingerprint_grow_ns.class_( "FingerScanUnmatchedTrigger", automation.Trigger.template() ) +FingerScanMisplacedTrigger = fingerprint_grow_ns.class_( + "FingerScanMisplacedTrigger", automation.Trigger.template() +) + +FingerScanInvalidTrigger = fingerprint_grow_ns.class_( + "FingerScanInvalidTrigger", automation.Trigger.template() +) + EnrollmentScanTrigger = fingerprint_grow_ns.class_( "EnrollmentScanTrigger", automation.Trigger.template(cg.uint8, cg.uint16) ) @@ -87,13 +104,35 @@ AURA_LED_COLORS = { } validate_aura_led_colors = cv.enum(AURA_LED_COLORS, upper=True) -CONFIG_SCHEMA = ( + +def validate(config): + if CONF_SENSOR_POWER_PIN in config and CONF_SENSING_PIN not in config: + raise cv.Invalid("You cannot use the Sensor Power Pin without a Sensing Pin") + if CONF_IDLE_PERIOD_TO_SLEEP in config and CONF_SENSOR_POWER_PIN not in config: + raise cv.Invalid( + "You cannot have an Idle Period to Sleep without a Sensor Power Pin" + ) + return config + + +CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(FingerprintGrowComponent), cv.Optional(CONF_SENSING_PIN): pins.gpio_input_pin_schema, + cv.Optional(CONF_SENSOR_POWER_PIN): pins.gpio_output_pin_schema, + cv.Optional( + CONF_IDLE_PERIOD_TO_SLEEP + ): cv.positive_time_period_milliseconds, cv.Optional(CONF_PASSWORD): cv.uint32_t, cv.Optional(CONF_NEW_PASSWORD): cv.uint32_t, + cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FingerScanStartTrigger + ), + } + ), cv.Optional(CONF_ON_FINGER_SCAN_MATCHED): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -108,6 +147,20 @@ CONFIG_SCHEMA = ( ), } ), + cv.Optional(CONF_ON_FINGER_SCAN_MISPLACED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FingerScanMisplacedTrigger + ), + } + ), + cv.Optional(CONF_ON_FINGER_SCAN_INVALID): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FingerScanInvalidTrigger + ), + } + ), cv.Optional(CONF_ON_ENROLLMENT_SCAN): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -132,7 +185,8 @@ CONFIG_SCHEMA = ( } ) .extend(cv.polling_component_schema("500ms")) - .extend(uart.UART_DEVICE_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA), + validate, ) @@ -152,6 +206,18 @@ async def to_code(config): sensing_pin = await cg.gpio_pin_expression(config[CONF_SENSING_PIN]) cg.add(var.set_sensing_pin(sensing_pin)) + if CONF_SENSOR_POWER_PIN in config: + sensor_power_pin = await cg.gpio_pin_expression(config[CONF_SENSOR_POWER_PIN]) + cg.add(var.set_sensor_power_pin(sensor_power_pin)) + + if CONF_IDLE_PERIOD_TO_SLEEP in config: + idle_period_to_sleep_ms = config[CONF_IDLE_PERIOD_TO_SLEEP] + cg.add(var.set_idle_period_to_sleep_ms(idle_period_to_sleep_ms)) + + for conf in config.get(CONF_ON_FINGER_SCAN_START, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_FINGER_SCAN_MATCHED, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation( @@ -162,6 +228,14 @@ async def to_code(config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_FINGER_SCAN_MISPLACED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_FINGER_SCAN_INVALID, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_ENROLLMENT_SCAN, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation( diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.cpp b/esphome/components/fingerprint_grow/fingerprint_grow.cpp index 8729e0f4bf..bd0817350a 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.cpp +++ b/esphome/components/fingerprint_grow/fingerprint_grow.cpp @@ -15,16 +15,23 @@ void FingerprintGrowComponent::update() { return; } - if (this->sensing_pin_ != nullptr) { + if (this->has_sensing_pin_) { + // A finger touch results in a low level (digital_read() == false) if (this->sensing_pin_->digital_read()) { ESP_LOGV(TAG, "No touch sensing"); this->waiting_removal_ = false; + if ((this->enrollment_image_ == 0) && // Not in enrolment process + (millis() - this->last_transfer_ms_ > this->idle_period_to_sleep_ms_) && (this->is_sensor_awake_)) { + this->sensor_sleep_(); + } return; + } else if (!this->waiting_removal_) { + this->finger_scan_start_callback_.call(); } } if (this->waiting_removal_) { - if (this->scan_image_(1) == NO_FINGER) { + if ((!this->has_sensing_pin_) && (this->scan_image_(1) == NO_FINGER)) { ESP_LOGD(TAG, "Finger removed"); this->waiting_removal_ = false; } @@ -51,6 +58,29 @@ void FingerprintGrowComponent::update() { void FingerprintGrowComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Grow Fingerprint Reader..."); + + this->has_sensing_pin_ = (this->sensing_pin_ != nullptr); + this->has_power_pin_ = (this->sensor_power_pin_ != nullptr); + + // Call pins setup, so we effectively apply the config generated from the yaml file. + if (this->has_sensing_pin_) { + this->sensing_pin_->setup(); + } + if (this->has_power_pin_) { + // Starts with output low (disabling power) to avoid glitches in the sensor + this->sensor_power_pin_->digital_write(false); + this->sensor_power_pin_->setup(); + + // If the user didn't specify an idle period to sleep, applies the default. + if (this->idle_period_to_sleep_ms_ == UINT32_MAX) { + this->idle_period_to_sleep_ms_ = DEFAULT_IDLE_PERIOD_TO_SLEEP_MS; + } + } + + // Place the sensor in a known (sleep/off) state and sync internal var state. + this->sensor_sleep_(); + delay(20); // This delay guarantees the sensor will in fact be powered power. + if (this->check_password_()) { if (this->new_password_ != -1) { if (this->set_password_()) @@ -91,7 +121,7 @@ void FingerprintGrowComponent::finish_enrollment(uint8_t result) { } void FingerprintGrowComponent::scan_and_match_() { - if (this->sensing_pin_ != nullptr) { + if (this->has_sensing_pin_) { ESP_LOGD(TAG, "Scan and match"); } else { ESP_LOGV(TAG, "Scan and match"); @@ -122,43 +152,52 @@ void FingerprintGrowComponent::scan_and_match_() { } uint8_t FingerprintGrowComponent::scan_image_(uint8_t buffer) { - if (this->sensing_pin_ != nullptr) { + if (this->has_sensing_pin_) { ESP_LOGD(TAG, "Getting image %d", buffer); } else { ESP_LOGV(TAG, "Getting image %d", buffer); } this->data_ = {GET_IMAGE}; - switch (this->send_command_()) { + uint8_t send_result = this->send_command_(); + switch (send_result) { case OK: break; case NO_FINGER: - if (this->sensing_pin_ != nullptr) { - ESP_LOGD(TAG, "No finger"); + if (this->has_sensing_pin_) { + this->waiting_removal_ = true; + ESP_LOGD(TAG, "Finger Misplaced"); + this->finger_scan_misplaced_callback_.call(); } else { ESP_LOGV(TAG, "No finger"); } - return this->data_[0]; + return send_result; case IMAGE_FAIL: ESP_LOGE(TAG, "Imaging error"); + this->finger_scan_invalid_callback_.call(); + return send_result; default: - return this->data_[0]; + ESP_LOGD(TAG, "Unknown Scan Error: %d", send_result); + return send_result; } ESP_LOGD(TAG, "Processing image %d", buffer); this->data_ = {IMAGE_2_TZ, buffer}; - switch (this->send_command_()) { + send_result = this->send_command_(); + switch (send_result) { case OK: ESP_LOGI(TAG, "Processed image %d", buffer); break; case IMAGE_MESS: ESP_LOGE(TAG, "Image too messy"); + this->finger_scan_invalid_callback_.call(); break; case FEATURE_FAIL: case INVALID_IMAGE: ESP_LOGE(TAG, "Could not find fingerprint features"); + this->finger_scan_invalid_callback_.call(); break; } - return this->data_[0]; + return send_result; } uint8_t FingerprintGrowComponent::save_fingerprint_() { @@ -221,10 +260,11 @@ bool FingerprintGrowComponent::get_parameters_() { ESP_LOGD(TAG, "Getting parameters"); this->data_ = {READ_SYS_PARAM}; if (this->send_command_() == OK) { - ESP_LOGD(TAG, "Got parameters"); - if (this->status_sensor_ != nullptr) { + ESP_LOGD(TAG, "Got parameters"); // Bear in mind data_[0] is the transfer status, + if (this->status_sensor_ != nullptr) { // the parameters table start at data_[1] this->status_sensor_->publish_state(((uint16_t) this->data_[1] << 8) | this->data_[2]); } + this->system_identifier_code_ = ((uint16_t) this->data_[3] << 8) | this->data_[4]; this->capacity_ = ((uint16_t) this->data_[5] << 8) | this->data_[6]; if (this->capacity_sensor_ != nullptr) { this->capacity_sensor_->publish_state(this->capacity_); @@ -322,7 +362,9 @@ void FingerprintGrowComponent::aura_led_control(uint8_t state, uint8_t speed, ui } } -uint8_t FingerprintGrowComponent::send_command_() { +uint8_t FingerprintGrowComponent::transfer_(std::vector *p_data_buffer) { + while (this->available()) + this->read(); this->write((uint8_t) (START_CODE >> 8)); this->write((uint8_t) (START_CODE & 0xFF)); this->write(this->address_[0]); @@ -331,12 +373,12 @@ uint8_t FingerprintGrowComponent::send_command_() { this->write(this->address_[3]); this->write(COMMAND); - uint16_t wire_length = this->data_.size() + 2; + uint16_t wire_length = p_data_buffer->size() + 2; this->write((uint8_t) (wire_length >> 8)); this->write((uint8_t) (wire_length & 0xFF)); uint16_t sum = ((wire_length) >> 8) + ((wire_length) &0xFF) + COMMAND; - for (auto data : this->data_) { + for (auto data : *p_data_buffer) { this->write(data); sum += data; } @@ -344,7 +386,7 @@ uint8_t FingerprintGrowComponent::send_command_() { this->write((uint8_t) (sum >> 8)); this->write((uint8_t) (sum & 0xFF)); - this->data_.clear(); + p_data_buffer->clear(); uint8_t byte; uint16_t idx = 0, length = 0; @@ -354,7 +396,9 @@ uint8_t FingerprintGrowComponent::send_command_() { delay(1); continue; } + byte = this->read(); + switch (idx) { case 0: if (byte != (uint8_t) (START_CODE >> 8)) @@ -388,9 +432,9 @@ uint8_t FingerprintGrowComponent::send_command_() { length |= byte; break; default: - this->data_.push_back(byte); + p_data_buffer->push_back(byte); if ((idx - 8) == length) { - switch (this->data_[0]) { + switch ((*p_data_buffer)[0]) { case OK: case NO_FINGER: case IMAGE_FAIL: @@ -410,29 +454,122 @@ uint8_t FingerprintGrowComponent::send_command_() { ESP_LOGE(TAG, "Reader failed to process request"); break; default: - ESP_LOGE(TAG, "Unknown response received from reader: %d", this->data_[0]); + ESP_LOGE(TAG, "Unknown response received from reader: 0x%.2X", (*p_data_buffer)[0]); break; } - return this->data_[0]; + this->last_transfer_ms_ = millis(); + return (*p_data_buffer)[0]; } break; } idx++; } ESP_LOGE(TAG, "No response received from reader"); - this->data_[0] = TIMEOUT; + (*p_data_buffer)[0] = TIMEOUT; + this->last_transfer_ms_ = millis(); return TIMEOUT; } +uint8_t FingerprintGrowComponent::send_command_() { + this->sensor_wakeup_(); + return this->transfer_(&this->data_); +} + +void FingerprintGrowComponent::sensor_wakeup_() { + // Immediately return if there is no power pin or the sensor is already on + if ((!this->has_power_pin_) || (this->is_sensor_awake_)) + return; + + this->sensor_power_pin_->digital_write(true); + this->is_sensor_awake_ = true; + + uint8_t byte = TIMEOUT; + + // Wait for the byte HANDSHAKE_SIGN from the sensor meaning it is operational. + for (uint16_t timer = 0; timer < WAIT_FOR_WAKE_UP_MS; timer++) { + if (this->available() > 0) { + byte = this->read(); + + /* If the received byte is zero, the UART probably misinterpreted a raising edge on + * the RX pin due the power up as byte "zero" - I verified this behaviour using + * the esp32-arduino lib. So here we just ignore this fake byte. + */ + if (byte != 0) + break; + } + delay(1); + } + + /* Lets check if the received by is a HANDSHAKE_SIGN, otherwise log an error + * message and try to continue on the best effort. + */ + if (byte == HANDSHAKE_SIGN) { + ESP_LOGD(TAG, "Sensor has woken up!"); + } else if (byte == TIMEOUT) { + ESP_LOGE(TAG, "Timed out waiting for sensor wake-up"); + } else { + ESP_LOGE(TAG, "Received wrong byte from the sensor during wake-up: 0x%.2X", byte); + } + + /* Next step, we must authenticate with the password. We cannot call check_password_ here + * neither use data_ to store the command because it might be already in use by the caller + * of send_command_() + */ + std::vector buffer = {VERIFY_PASSWORD, (uint8_t) (this->password_ >> 24), (uint8_t) (this->password_ >> 16), + (uint8_t) (this->password_ >> 8), (uint8_t) (this->password_ & 0xFF)}; + + if (this->transfer_(&buffer) != OK) { + ESP_LOGE(TAG, "Wrong password"); + } +} + +void FingerprintGrowComponent::sensor_sleep_() { + // Immediately return if the power pin feature is not implemented + if (!this->has_power_pin_) + return; + + this->sensor_power_pin_->digital_write(false); + this->is_sensor_awake_ = false; + ESP_LOGD(TAG, "Fingerprint sensor is now in sleep mode."); +} + void FingerprintGrowComponent::dump_config() { ESP_LOGCONFIG(TAG, "GROW_FINGERPRINT_READER:"); + ESP_LOGCONFIG(TAG, " System Identifier Code: 0x%.4X", this->system_identifier_code_); + ESP_LOGCONFIG(TAG, " Touch Sensing Pin: %s", + this->has_sensing_pin_ ? this->sensing_pin_->dump_summary().c_str() : "None"); + ESP_LOGCONFIG(TAG, " Sensor Power Pin: %s", + this->has_power_pin_ ? this->sensor_power_pin_->dump_summary().c_str() : "None"); + if (this->idle_period_to_sleep_ms_ < UINT32_MAX) { + ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %u ms", this->idle_period_to_sleep_ms_); + } else { + ESP_LOGCONFIG(TAG, " Idle Period to Sleep: Never"); + } LOG_UPDATE_INTERVAL(this); - LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_); - LOG_SENSOR(" ", "Status", this->status_sensor_); - LOG_SENSOR(" ", "Capacity", this->capacity_sensor_); - LOG_SENSOR(" ", "Security Level", this->security_level_sensor_); - LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_); - LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_); + if (this->fingerprint_count_sensor_) { + LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->fingerprint_count_sensor_->get_state()); + } + if (this->status_sensor_) { + LOG_SENSOR(" ", "Status", this->status_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->status_sensor_->get_state()); + } + if (this->capacity_sensor_) { + LOG_SENSOR(" ", "Capacity", this->capacity_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->capacity_sensor_->get_state()); + } + if (this->security_level_sensor_) { + LOG_SENSOR(" ", "Security Level", this->security_level_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->security_level_sensor_->get_state()); + } + if (this->last_finger_id_sensor_) { + LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_finger_id_sensor_->get_state()); + } + if (this->last_confidence_sensor_) { + LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_confidence_sensor_->get_state()); + } } } // namespace fingerprint_grow diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.h b/esphome/components/fingerprint_grow/fingerprint_grow.h index f414146e64..20ff60997b 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.h +++ b/esphome/components/fingerprint_grow/fingerprint_grow.h @@ -15,6 +15,11 @@ static const uint16_t START_CODE = 0xEF01; static const uint16_t ENROLLMENT_SLOT_UNUSED = 0xFFFF; +// The datasheet says a max wake up time of of 200ms. +static const uint8_t WAIT_FOR_WAKE_UP_MS = 200; + +static const uint32_t DEFAULT_IDLE_PERIOD_TO_SLEEP_MS = 5000; + enum GrowPacketType { COMMAND = 0x01, DATA = 0x02, @@ -63,6 +68,7 @@ enum GrowResponse { INVALID_IMAGE = 0x15, FLASH_ERR = 0x18, INVALID_REG = 0x1A, + HANDSHAKE_SIGN = 0x55, BAD_PACKET = 0xFE, TIMEOUT = 0xFF, }; @@ -99,8 +105,10 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic this->address_[3] = (uint8_t) (address & 0xFF); } void set_sensing_pin(GPIOPin *sensing_pin) { this->sensing_pin_ = sensing_pin; } + void set_sensor_power_pin(GPIOPin *sensor_power_pin) { this->sensor_power_pin_ = sensor_power_pin; } void set_password(uint32_t password) { this->password_ = password; } void set_new_password(uint32_t new_password) { this->new_password_ = new_password; } + void set_idle_period_to_sleep_ms(uint32_t period_ms) { this->idle_period_to_sleep_ms_ = period_ms; } void set_fingerprint_count_sensor(sensor::Sensor *fingerprint_count_sensor) { this->fingerprint_count_sensor_ = fingerprint_count_sensor; } @@ -118,12 +126,21 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic void set_enrolling_binary_sensor(binary_sensor::BinarySensor *enrolling_binary_sensor) { this->enrolling_binary_sensor_ = enrolling_binary_sensor; } + void add_on_finger_scan_start_callback(std::function callback) { + this->finger_scan_start_callback_.add(std::move(callback)); + } void add_on_finger_scan_matched_callback(std::function callback) { this->finger_scan_matched_callback_.add(std::move(callback)); } void add_on_finger_scan_unmatched_callback(std::function callback) { this->finger_scan_unmatched_callback_.add(std::move(callback)); } + void add_on_finger_scan_misplaced_callback(std::function callback) { + this->finger_scan_misplaced_callback_.add(std::move(callback)); + } + void add_on_finger_scan_invalid_callback(std::function callback) { + this->finger_scan_invalid_callback_.add(std::move(callback)); + } void add_on_enrollment_scan_callback(std::function callback) { this->enrollment_scan_callback_.add(std::move(callback)); } @@ -151,7 +168,10 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic bool set_password_(); bool get_parameters_(); void get_fingerprint_count_(); + uint8_t transfer_(std::vector *p_data_buffer); uint8_t send_command_(); + void sensor_wakeup_(); + void sensor_sleep_(); std::vector data_ = {}; uint8_t address_[4] = {0xFF, 0xFF, 0xFF, 0xFF}; @@ -159,12 +179,19 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic uint32_t password_ = 0x0; uint32_t new_password_ = -1; GPIOPin *sensing_pin_{nullptr}; + GPIOPin *sensor_power_pin_{nullptr}; uint8_t enrollment_image_ = 0; uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED; uint8_t enrollment_buffers_ = 5; bool waiting_removal_ = false; + bool has_sensing_pin_ = false; + bool has_power_pin_ = false; + bool is_sensor_awake_ = false; + uint32_t last_transfer_ms_ = 0; uint32_t last_aura_led_control_ = 0; uint16_t last_aura_led_duration_ = 0; + uint16_t system_identifier_code_ = 0; + uint32_t idle_period_to_sleep_ms_ = UINT32_MAX; sensor::Sensor *fingerprint_count_sensor_{nullptr}; sensor::Sensor *status_sensor_{nullptr}; sensor::Sensor *capacity_sensor_{nullptr}; @@ -172,13 +199,23 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic sensor::Sensor *last_finger_id_sensor_{nullptr}; sensor::Sensor *last_confidence_sensor_{nullptr}; binary_sensor::BinarySensor *enrolling_binary_sensor_{nullptr}; + CallbackManager finger_scan_invalid_callback_; + CallbackManager finger_scan_start_callback_; CallbackManager finger_scan_matched_callback_; CallbackManager finger_scan_unmatched_callback_; + CallbackManager finger_scan_misplaced_callback_; CallbackManager enrollment_scan_callback_; CallbackManager enrollment_done_callback_; CallbackManager enrollment_failed_callback_; }; +class FingerScanStartTrigger : public Trigger<> { + public: + explicit FingerScanStartTrigger(FingerprintGrowComponent *parent) { + parent->add_on_finger_scan_start_callback([this]() { this->trigger(); }); + } +}; + class FingerScanMatchedTrigger : public Trigger { public: explicit FingerScanMatchedTrigger(FingerprintGrowComponent *parent) { @@ -194,6 +231,20 @@ class FingerScanUnmatchedTrigger : public Trigger<> { } }; +class FingerScanMisplacedTrigger : public Trigger<> { + public: + explicit FingerScanMisplacedTrigger(FingerprintGrowComponent *parent) { + parent->add_on_finger_scan_misplaced_callback([this]() { this->trigger(); }); + } +}; + +class FingerScanInvalidTrigger : public Trigger<> { + public: + explicit FingerScanInvalidTrigger(FingerprintGrowComponent *parent) { + parent->add_on_finger_scan_invalid_callback([this]() { this->trigger(); }); + } +}; + class EnrollmentScanTrigger : public Trigger { public: explicit EnrollmentScanTrigger(FingerprintGrowComponent *parent) { diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 22a5f6b2c5..76eb05e6ad 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -1,111 +1,141 @@ +import hashlib +import logging + import functools from pathlib import Path -import hashlib import os import re from packaging import version - import requests from esphome import core +from esphome import external_files import esphome.config_validation as cv import esphome.codegen as cg -from esphome.helpers import copy_file_if_changed +from esphome.helpers import ( + copy_file_if_changed, + cpp_string_escape, +) from esphome.const import ( + __version__, CONF_FAMILY, CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_RAW_DATA_ID, CONF_TYPE, + CONF_REFRESH, CONF_SIZE, CONF_PATH, CONF_WEIGHT, + CONF_URL, +) +from esphome.core import ( + CORE, + HexInt, ) -from esphome.core import CORE, HexInt +_LOGGER = logging.getLogger(__name__) DOMAIN = "font" DEPENDENCIES = ["display"] MULTI_CONF = True +CODEOWNERS = ["@esphome/core", "@clydebarrow"] + font_ns = cg.esphome_ns.namespace("font") Font = font_ns.class_("Font") Glyph = font_ns.class_("Glyph") GlyphData = font_ns.struct("GlyphData") +CONF_BPP = "bpp" +CONF_EXTRAS = "extras" +CONF_FONTS = "fonts" + + +def glyph_comparator(x, y): + x_ = x.encode("utf-8") + y_ = y.encode("utf-8") + + for c in range(min(len(x_), len(y_))): + if x_[c] < y_[c]: + return -1 + if x_[c] > y_[c]: + return 1 + + if len(x_) < len(y_): + return -1 + if len(x_) > len(y_): + return 1 + raise cv.Invalid(f"Found duplicate glyph {x}") + def validate_glyphs(value): if isinstance(value, list): value = cv.Schema([cv.string])(value) value = cv.Schema([cv.string])(list(value)) - def comparator(x, y): - x_ = x.encode("utf-8") - y_ = y.encode("utf-8") - - for c in range(min(len(x_), len(y_))): - if x_[c] < y_[c]: - return -1 - if x_[c] > y_[c]: - return 1 - - if len(x_) < len(y_): - return -1 - if len(x_) > len(y_): - return 1 - raise cv.Invalid(f"Found duplicate glyph {x}") - - value.sort(key=functools.cmp_to_key(comparator)) + value.sort(key=functools.cmp_to_key(glyph_comparator)) return value +font_map = {} + + +def merge_glyphs(config): + glyphs = [] + glyphs.extend(config[CONF_GLYPHS]) + font_list = [(EFont(config[CONF_FILE], config[CONF_SIZE], config[CONF_GLYPHS]))] + if extras := config.get(CONF_EXTRAS): + extra_fonts = list( + map( + lambda x: EFont(x[CONF_FILE], config[CONF_SIZE], x[CONF_GLYPHS]), extras + ) + ) + font_list.extend(extra_fonts) + for extra in extras: + glyphs.extend(extra[CONF_GLYPHS]) + validate_glyphs(glyphs) + font_map[config[CONF_ID]] = font_list + return config + + def validate_pillow_installed(value): try: import PIL except ImportError as err: raise cv.Invalid( "Please install the pillow python package to use this feature. " - '(pip install "pillow==10.1.0")' + '(pip install "pillow==10.2.0")' ) from err - if version.parse(PIL.__version__) != version.parse("10.1.0"): + if version.parse(PIL.__version__) != version.parse("10.2.0"): raise cv.Invalid( - "Please update your pillow installation to 10.1.0. " - '(pip install "pillow==10.1.0")' + "Please update your pillow installation to 10.2.0. " + '(pip install "pillow==10.2.0")' ) return value +FONT_EXTENSIONS = (".ttf", ".woff", ".otf") + + def validate_truetype_file(value): - if value.endswith(".zip"): # for Google Fonts downloads + if value.lower().endswith(".zip"): # for Google Fonts downloads raise cv.Invalid( f"Please unzip the font archive '{value}' first and then use the .ttf files inside." ) - if not value.endswith(".ttf"): - raise cv.Invalid( - "Only truetype (.ttf) files are supported. Please make sure you're " - "using the correct format or rename the extension to .ttf" - ) + if not any(map(value.lower().endswith, FONT_EXTENSIONS)): + raise cv.Invalid(f"Only {FONT_EXTENSIONS} files are supported.") return cv.file_(value) -def _compute_local_font_dir(name) -> Path: - h = hashlib.new("sha256") - h.update(name.encode()) - return Path(CORE.data_dir) / DOMAIN / h.hexdigest()[:8] - - -def _compute_gfonts_local_path(value) -> Path: - name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1" - return _compute_local_font_dir(name) / "font.ttf" - - TYPE_LOCAL = "local" TYPE_LOCAL_BITMAP = "local_bitmap" TYPE_GFONTS = "gfonts" +TYPE_WEB = "web" LOCAL_SCHEMA = cv.Schema( { cv.Required(CONF_PATH): validate_truetype_file, @@ -136,21 +166,64 @@ def validate_weight_name(value): return FONT_WEIGHTS[cv.one_of(*FONT_WEIGHTS, lower=True, space="-")(value)] -def download_gfonts(value): +def _compute_local_font_path(value: dict) -> Path: + url = value[CONF_URL] + h = hashlib.new("sha256") + h.update(url.encode()) + key = h.hexdigest()[:8] + base_dir = external_files.compute_local_file_dir(DOMAIN) + _LOGGER.debug("_compute_local_font_path: base_dir=%s", base_dir / key) + return base_dir / key + + +def get_font_path(value, type) -> Path: + if type == TYPE_GFONTS: + name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1" + return external_files.compute_local_file_dir(DOMAIN) / f"{name}.ttf" + if type == TYPE_WEB: + return _compute_local_font_path(value) / "font.ttf" + return None + + +def download_content(url: str, path: Path) -> None: + if not external_files.has_remote_file_changed(url, path): + _LOGGER.debug("Remote file has not changed %s", url) + return + + _LOGGER.debug( + "Remote file has changed, downloading from %s to %s", + url, + path, + ) + + try: + req = requests.get( + url, + timeout=external_files.NETWORK_TIMEOUT, + headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"}, + ) + req.raise_for_status() + except requests.exceptions.RequestException as e: + raise cv.Invalid(f"Could not download from {url}: {e}") + + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(req.content) + + +def download_gfont(value): name = ( f"{value[CONF_FAMILY]}:ital,wght@{int(value[CONF_ITALIC])},{value[CONF_WEIGHT]}" ) url = f"https://fonts.googleapis.com/css2?family={name}" + path = get_font_path(value, TYPE_GFONTS) + _LOGGER.debug("download_gfont: path=%s", path) - path = _compute_gfonts_local_path(value) - if path.is_file(): - return value try: - req = requests.get(url, timeout=30) + req = requests.get(url, timeout=external_files.NETWORK_TIMEOUT) req.raise_for_status() except requests.exceptions.RequestException as e: raise cv.Invalid( - f"Could not download font for {name}, please check the fonts exists " + f"Could not download font at {url}, please check the fonts exists " f"at google fonts ({e})" ) match = re.search(r"src:\s+url\((.+)\)\s+format\('truetype'\);", req.text) @@ -161,26 +234,48 @@ def download_gfonts(value): ) ttf_url = match.group(1) - try: - req = requests.get(ttf_url, timeout=30) - req.raise_for_status() - except requests.exceptions.RequestException as e: - raise cv.Invalid(f"Could not download ttf file for {name} ({ttf_url}): {e}") + _LOGGER.debug("download_gfont: ttf_url=%s", ttf_url) - path.parent.mkdir(exist_ok=True, parents=True) - path.write_bytes(req.content) + download_content(ttf_url, path) return value -GFONTS_SCHEMA = cv.All( +def download_web_font(value): + url = value[CONF_URL] + path = get_font_path(value, TYPE_WEB) + + download_content(url, path) + _LOGGER.debug("download_web_font: path=%s", path) + return value + + +EXTERNAL_FONT_SCHEMA = cv.Schema( { - cv.Required(CONF_FAMILY): cv.string_strict, cv.Optional(CONF_WEIGHT, default="regular"): cv.Any( cv.int_, validate_weight_name ), cv.Optional(CONF_ITALIC, default=False): cv.boolean, - }, - download_gfonts, + cv.Optional(CONF_REFRESH, default="1d"): cv.All(cv.string, cv.source_refresh), + } +) + + +GFONTS_SCHEMA = cv.All( + EXTERNAL_FONT_SCHEMA.extend( + { + cv.Required(CONF_FAMILY): cv.string_strict, + } + ), + download_gfont, +) + +WEB_FONT_SCHEMA = cv.All( + EXTERNAL_FONT_SCHEMA.extend( + { + cv.Required(CONF_URL): cv.string_strict, + } + ), + download_web_font, ) @@ -200,6 +295,14 @@ def validate_file_shorthand(value): data[CONF_WEIGHT] = weight[1:] return FILE_SCHEMA(data) + if value.startswith("http://") or value.startswith("https://"): + return FILE_SCHEMA( + { + CONF_TYPE: TYPE_WEB, + CONF_URL: value, + } + ) + if value.endswith(".pcf") or value.endswith(".bdf"): return FILE_SCHEMA( { @@ -221,6 +324,7 @@ TYPED_FILE_SCHEMA = cv.typed_schema( TYPE_LOCAL: LOCAL_SCHEMA, TYPE_GFONTS: GFONTS_SCHEMA, TYPE_LOCAL_BITMAP: LOCAL_BITMAP_SCHEMA, + TYPE_WEB: WEB_FONT_SCHEMA, } ) @@ -231,11 +335,10 @@ def _file_schema(value): return TYPED_FILE_SCHEMA(value) -FILE_SCHEMA = cv.Schema(_file_schema) - +FILE_SCHEMA = cv.All(_file_schema) DEFAULT_GLYPHS = ( - ' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' + ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' ) CONF_RAW_GLYPH_ID = "raw_glyph_id" @@ -245,12 +348,22 @@ FONT_SCHEMA = cv.Schema( cv.Required(CONF_FILE): FILE_SCHEMA, cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs, cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1), + cv.Optional(CONF_BPP, default=1): cv.one_of(1, 2, 4, 8), + cv.Optional(CONF_EXTRAS): cv.ensure_list( + cv.Schema( + { + cv.Required(CONF_FILE): FILE_SCHEMA, + cv.Required(CONF_GLYPHS): validate_glyphs, + } + ) + ), cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(GlyphData), - } + }, ) -CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA) +CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA, merge_glyphs) + # PIL doesn't provide a consistent interface for both TrueType and bitmap # fonts. So, we use our own wrappers to give us the consistency that we need. @@ -292,13 +405,37 @@ class BitmapFontWrapper: return (max_height, 0) -def convert_bitmap_to_pillow_font(filepath): - from PIL import PcfFontFile, BdfFontFile +class EFont: + def __init__(self, file, size, glyphs): + self.glyphs = glyphs + ftype = file[CONF_TYPE] + if ftype == TYPE_LOCAL_BITMAP: + font = load_bitmap_font(CORE.relative_config_path(file[CONF_PATH])) + elif ftype == TYPE_LOCAL: + path = CORE.relative_config_path(file[CONF_PATH]) + font = load_ttf_font(path, size) + elif ftype in (TYPE_GFONTS, TYPE_WEB): + path = get_font_path(file, ftype) + font = load_ttf_font(path, size) + else: + raise cv.Invalid(f"Could not load font: unknown type: {ftype}") + self.font = font + self.ascent, self.descent = font.getmetrics(glyphs) - local_bitmap_font_file = _compute_local_font_dir(filepath) / os.path.basename( - filepath + def has_glyph(self, glyph): + return glyph in self.glyphs + + +def convert_bitmap_to_pillow_font(filepath): + from PIL import ( + PcfFontFile, + BdfFontFile, ) + local_bitmap_font_file = external_files.compute_local_file_dir( + DOMAIN, + ) / os.path.basename(filepath) + copy_file_if_changed(filepath, local_bitmap_font_file) with open(local_bitmap_font_file, "rb") as fp: @@ -347,60 +484,82 @@ def load_ttf_font(path, size): return TrueTypeFontWrapper(font) +class GlyphInfo: + def __init__(self, data_len, offset_x, offset_y, width, height): + self.data_len = data_len + self.offset_x = offset_x + self.offset_y = offset_y + self.width = width + self.height = height + + async def to_code(config): - conf = config[CONF_FILE] - if conf[CONF_TYPE] == TYPE_LOCAL_BITMAP: - font = load_bitmap_font(CORE.relative_config_path(conf[CONF_PATH])) - elif conf[CONF_TYPE] == TYPE_LOCAL: - path = CORE.relative_config_path(conf[CONF_PATH]) - font = load_ttf_font(path, config[CONF_SIZE]) - elif conf[CONF_TYPE] == TYPE_GFONTS: - path = _compute_gfonts_local_path(conf) - font = load_ttf_font(path, config[CONF_SIZE]) - else: - raise core.EsphomeError(f"Could not load font: unknown type: {conf[CONF_TYPE]}") - - ascent, descent = font.getmetrics(config[CONF_GLYPHS]) - + glyph_to_font_map = {} + font_list = font_map[config[CONF_ID]] + glyphs = [] + for font in font_list: + glyphs.extend(font.glyphs) + for glyph in font.glyphs: + glyph_to_font_map[glyph] = font + glyphs.sort(key=functools.cmp_to_key(glyph_comparator)) glyph_args = {} data = [] - for glyph in config[CONF_GLYPHS]: - mask = font.getmask(glyph, mode="1") + bpp = config[CONF_BPP] + if bpp == 1: + mode = "1" + scale = 1 + else: + mode = "L" + scale = 256 // (1 << bpp) + for glyph in glyphs: + font = glyph_to_font_map[glyph].font + mask = font.getmask(glyph, mode=mode) offset_x, offset_y = font.getoffset(glyph) width, height = mask.size - width8 = ((width + 7) // 8) * 8 - glyph_data = [0] * (height * width8 // 8) + glyph_data = [0] * ((height * width * bpp + 7) // 8) + pos = 0 for y in range(height): for x in range(width): - if not mask.getpixel((x, y)): - continue - pos = x + y * width8 - glyph_data[pos // 8] |= 0x80 >> (pos % 8) - glyph_args[glyph] = (len(data), offset_x, offset_y, width, height) + pixel = mask.getpixel((x, y)) // scale + for bit_num in range(bpp): + if pixel & (1 << (bpp - bit_num - 1)): + glyph_data[pos // 8] |= 0x80 >> (pos % 8) + pos += 1 + glyph_args[glyph] = GlyphInfo(len(data), offset_x, offset_y, width, height) data += glyph_data rhs = [HexInt(x) for x in data] prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) glyph_initializer = [] - for glyph in config[CONF_GLYPHS]: + for glyph in glyphs: glyph_initializer.append( cg.StructInitializer( GlyphData, - ("a_char", glyph), + ( + "a_char", + cg.RawExpression(f"(const uint8_t *){cpp_string_escape(glyph)}"), + ), ( "data", - cg.RawExpression(f"{str(prog_arr)} + {str(glyph_args[glyph][0])}"), + cg.RawExpression( + f"{str(prog_arr)} + {str(glyph_args[glyph].data_len)}" + ), ), - ("offset_x", glyph_args[glyph][1]), - ("offset_y", glyph_args[glyph][2]), - ("width", glyph_args[glyph][3]), - ("height", glyph_args[glyph][4]), + ("offset_x", glyph_args[glyph].offset_x), + ("offset_y", glyph_args[glyph].offset_y), + ("width", glyph_args[glyph].width), + ("height", glyph_args[glyph].height), ) ) glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer) cg.new_Pvariable( - config[CONF_ID], glyphs, len(glyph_initializer), ascent, ascent + descent + config[CONF_ID], + glyphs, + len(glyph_initializer), + font_list[0].ascent, + font_list[0].ascent + font_list[0].descent, + bpp, ) diff --git a/esphome/components/font/font.cpp b/esphome/components/font/font.cpp index ef5b2b788d..5a18429789 100644 --- a/esphome/components/font/font.cpp +++ b/esphome/components/font/font.cpp @@ -10,29 +10,10 @@ namespace font { static const char *const TAG = "font"; -void Glyph::draw(int x_at, int y_start, display::Display *display, Color color) const { - int scan_x1, scan_y1, scan_width, scan_height; - this->scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); - - const unsigned char *data = this->glyph_data_->data; - const int max_x = x_at + scan_x1 + scan_width; - const int max_y = y_start + scan_y1 + scan_height; - - for (int glyph_y = y_start + scan_y1; glyph_y < max_y; glyph_y++) { - for (int glyph_x = x_at + scan_x1; glyph_x < max_x; data++, glyph_x += 8) { - uint8_t pixel_data = progmem_read_byte(data); - const int pixel_max_x = std::min(max_x, glyph_x + 8); - - for (int pixel_x = glyph_x; pixel_x < pixel_max_x && pixel_data; pixel_x++, pixel_data <<= 1) { - if (pixel_data & 0x80) { - display->draw_pixel_at(pixel_x, glyph_y, color); - } - } - } - } -} -const char *Glyph::get_char() const { return this->glyph_data_->a_char; } -bool Glyph::compare_to(const char *str) const { +const uint8_t *Glyph::get_char() const { return this->glyph_data_->a_char; } +// Compare the char at the string position with this char. +// Return true if this char is less than or equal the other. +bool Glyph::compare_to(const uint8_t *str) const { // 1 -> this->char_ // 2 -> str for (uint32_t i = 0;; i++) { @@ -48,7 +29,7 @@ bool Glyph::compare_to(const char *str) const { // this should not happen return false; } -int Glyph::match_length(const char *str) const { +int Glyph::match_length(const uint8_t *str) const { for (uint32_t i = 0;; i++) { if (this->glyph_data_->a_char[i] == '\0') return i; @@ -65,12 +46,13 @@ void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const { *height = this->glyph_data_->height; } -Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) { +Font::Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp) + : baseline_(baseline), height_(height), bpp_(bpp) { glyphs_.reserve(data_nr); for (int i = 0; i < data_nr; ++i) glyphs_.emplace_back(&data[i]); } -int Font::match_next_glyph(const char *str, int *match_length) { +int Font::match_next_glyph(const uint8_t *str, int *match_length) { int lo = 0; int hi = this->glyphs_.size() - 1; while (lo != hi) { @@ -95,7 +77,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in int x = 0; while (str[i] != '\0') { int match_length; - int glyph_n = this->match_next_glyph(str + i, &match_length); + int glyph_n = this->match_next_glyph((const uint8_t *) str + i, &match_length); if (glyph_n < 0) { // Unknown char, skip if (!this->get_glyphs().empty()) @@ -118,12 +100,13 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in *x_offset = min_x; *width = x - min_x; } -void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text) { +void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text, Color background) { int i = 0; int x_at = x_start; + int scan_x1, scan_y1, scan_width, scan_height; while (text[i] != '\0') { int match_length; - int glyph_n = this->match_next_glyph(text + i, &match_length); + int glyph_n = this->match_next_glyph((const uint8_t *) text + i, &match_length); if (glyph_n < 0) { // Unknown char, skip ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]); @@ -138,7 +121,41 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo } const Glyph &glyph = this->get_glyphs()[glyph_n]; - glyph.draw(x_at, y_start, display, color); + glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); + + const uint8_t *data = glyph.glyph_data_->data; + const int max_x = x_at + scan_x1 + scan_width; + const int max_y = y_start + scan_y1 + scan_height; + + uint8_t bitmask = 0; + uint8_t pixel_data = 0; + float bpp_max = (1 << this->bpp_) - 1; + for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) { + for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) { + uint8_t pixel = 0; + for (int bit_num = 0; bit_num != this->bpp_; bit_num++) { + if (bitmask == 0) { + pixel_data = progmem_read_byte(data++); + bitmask = 0x80; + } + pixel <<= 1; + if ((pixel_data & bitmask) != 0) + pixel |= 1; + bitmask >>= 1; + } + if (pixel == bpp_max) { + display->draw_pixel_at(glyph_x, glyph_y, color); + } else if (pixel != 0) { + float on = (float) pixel / bpp_max; + float off = 1.0 - on; + Color blended; + blended.r = color.r * on + background.r * off; + blended.g = color.r * on + background.g * off; + blended.b = color.r * on + background.b * off; + display->draw_pixel_at(glyph_x, glyph_y, blended); + } + } + } x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; i += match_length; diff --git a/esphome/components/font/font.h b/esphome/components/font/font.h index 03171a6126..57002cf510 100644 --- a/esphome/components/font/font.h +++ b/esphome/components/font/font.h @@ -10,7 +10,7 @@ namespace font { class Font; struct GlyphData { - const char *a_char; + const uint8_t *a_char; const uint8_t *data; int offset_x; int offset_y; @@ -22,16 +22,16 @@ class Glyph { public: Glyph(const GlyphData *data) : glyph_data_(data) {} - void draw(int x, int y, display::Display *display, Color color) const; + const uint8_t *get_char() const; - const char *get_char() const; + bool compare_to(const uint8_t *str) const; - bool compare_to(const char *str) const; - - int match_length(const char *str) const; + int match_length(const uint8_t *str) const; void scan_area(int *x1, int *y1, int *width, int *height) const; + const GlyphData *get_glyph_data() const { return this->glyph_data_; } + protected: friend Font; @@ -46,14 +46,16 @@ class Font : public display::BaseFont { * @param baseline The y-offset from the top of the text to the baseline. * @param bottom The y-offset from the top of the text to the bottom (i.e. height). */ - Font(const GlyphData *data, int data_nr, int baseline, int height); + Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp = 1); - int match_next_glyph(const char *str, int *match_length); + int match_next_glyph(const uint8_t *str, int *match_length); - void print(int x_start, int y_start, display::Display *display, Color color, const char *text) override; + void print(int x_start, int y_start, display::Display *display, Color color, const char *text, + Color background) override; void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override; inline int get_baseline() { return this->baseline_; } inline int get_height() { return this->height_; } + inline int get_bpp() { return this->bpp_; } const std::vector> &get_glyphs() const { return glyphs_; } @@ -61,6 +63,7 @@ class Font : public display::BaseFont { std::vector> glyphs_; int baseline_; int height_; + uint8_t bpp_; // bits per pixel }; } // namespace font diff --git a/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h b/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h index 0b3a2c1b86..7ddd2e44d7 100644 --- a/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h +++ b/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h @@ -66,8 +66,14 @@ class FT5x06Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice return; } // reading the chip registers to get max x/y does not seem to work. - this->x_raw_max_ = this->display_->get_width(); - this->y_raw_max_ = this->display_->get_height(); + if (this->display_ != nullptr) { + if (this->x_raw_max_ == this->x_raw_min_) { + this->x_raw_max_ = this->display_->get_native_width(); + } + if (this->y_raw_max_ == this->y_raw_min_) { + this->y_raw_max_ = this->display_->get_native_height(); + } + } esph_log_config(TAG, "FT5x06 Touchscreen setup complete"); } diff --git a/esphome/components/ft63x6/ft63x6.cpp b/esphome/components/ft63x6/ft63x6.cpp index b674ded22c..fe64f76fac 100644 --- a/esphome/components/ft63x6/ft63x6.cpp +++ b/esphome/components/ft63x6/ft63x6.cpp @@ -12,21 +12,23 @@ // Reference: https://focuslcds.com/content/FT6236.pdf namespace esphome { namespace ft63x6 { +static const uint8_t FT6X36_ADDR_DEVICE_MODE = 0x00; -static const uint8_t FT63X6_ADDR_TOUCH_COUNT = 0x02; - -static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05; +static const uint8_t FT63X6_ADDR_TD_STATUS = 0x02; +static const uint8_t FT63X6_ADDR_TOUCH1_STATE = 0x03; static const uint8_t FT63X6_ADDR_TOUCH1_X = 0x03; +static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05; static const uint8_t FT63X6_ADDR_TOUCH1_Y = 0x05; +static const uint8_t FT63X6_ADDR_TOUCH1_WEIGHT = 0x07; +static const uint8_t FT63X6_ADDR_TOUCH1_MISC = 0x08; +static const uint8_t FT6X36_ADDR_THRESHHOLD = 0x80; +static const uint8_t FT6X36_ADDR_TOUCHRATE_ACTIVE = 0x88; +static const uint8_t FT63X6_ADDR_CHIP_ID = 0xA3; -static const uint8_t FT63X6_ADDR_TOUCH2_ID = 0x0B; -static const uint8_t FT63X6_ADDR_TOUCH2_X = 0x09; -static const uint8_t FT63X6_ADDR_TOUCH2_Y = 0x0B; - -static const char *const TAG = "FT63X6Touchscreen"; +static const char *const TAG = "FT63X6"; void FT63X6Touchscreen::setup() { - ESP_LOGCONFIG(TAG, "Setting up FT63X6Touchscreen Touchscreen..."); + ESP_LOGCONFIG(TAG, "Setting up FT63X6 Touchscreen..."); if (this->interrupt_pin_ != nullptr) { this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); this->interrupt_pin_->setup(); @@ -35,32 +37,25 @@ void FT63X6Touchscreen::setup() { if (this->reset_pin_ != nullptr) { this->reset_pin_->setup(); + this->hard_reset_(); } - this->hard_reset_(); - // Get touch resolution - this->x_raw_max_ = 320; - this->y_raw_max_ = 480; -} - -void FT63X6Touchscreen::update_touches() { - int touch_count = this->read_touch_count_(); - if (touch_count == 0) { - return; + if (this->x_raw_max_ == this->x_raw_min_) { + this->x_raw_max_ = 320; } - - uint8_t touch_id = this->read_touch_id_(FT63X6_ADDR_TOUCH1_ID); // id1 = 0 or 1 - int16_t x = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH1_X); - int16_t y = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH1_Y); - this->add_raw_touch_position_(touch_id, x, y); - - if (touch_count >= 2) { - touch_id = this->read_touch_id_(FT63X6_ADDR_TOUCH2_ID); // id2 = 0 or 1(~id1 & 0x01) - x = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH2_X); - y = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH2_Y); - this->add_raw_touch_position_(touch_id, x, y); + if (this->y_raw_max_ == this->y_raw_min_) { + this->y_raw_max_ = 480; } + uint8_t chip_id = this->read_byte_(FT63X6_ADDR_CHIP_ID); + if (chip_id != 0) { + ESP_LOGI(TAG, "FT6336U touch driver started chipid: %d", chip_id); + } else { + ESP_LOGE(TAG, "FT6336U touch driver failed to start"); + } + this->write_byte(FT6X36_ADDR_DEVICE_MODE, 0x00); + this->write_byte(FT6X36_ADDR_THRESHHOLD, this->threshold_); + this->write_byte(FT6X36_ADDR_TOUCHRATE_ACTIVE, 0x0E); } void FT63X6Touchscreen::hard_reset_() { @@ -76,18 +71,59 @@ void FT63X6Touchscreen::dump_config() { LOG_I2C_DEVICE(this); LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_UPDATE_INTERVAL(this); } -uint8_t FT63X6Touchscreen::read_touch_count_() { return this->read_byte_(FT63X6_ADDR_TOUCH_COUNT); } +void FT63X6Touchscreen::update_touches() { + uint16_t touch_id, x, y; -// Touch functions -uint16_t FT63X6Touchscreen::read_touch_coordinate_(uint8_t coordinate) { + uint8_t touches = this->read_touch_number_(); + if ((touches == 0x00) || (touches == 0xff)) { + // ESP_LOGD(TAG, "No touches detected"); + return; + } + + ESP_LOGV(TAG, "Touches found: %d", touches); + + for (auto point = 0; point < touches; point++) { + if (((this->read_touch_event_(point)) & 0x01) == 0) { // checking event flag bit 6 if it is null + touch_id = this->read_touch_id_(point); // id1 = 0 or 1 + x = this->read_touch_x_(point); + y = this->read_touch_y_(point); + if ((x == 0) && (y == 0)) { + ESP_LOGW(TAG, "Reporting a (0,0) touch on %d", touch_id); + } + this->add_raw_touch_position_(touch_id, x, y, this->read_touch_weight_(point)); + } + } +} + +uint8_t FT63X6Touchscreen::read_touch_number_() { return this->read_byte_(FT63X6_ADDR_TD_STATUS) & 0x0F; } +// Touch 1 functions +uint16_t FT63X6Touchscreen::read_touch_x_(uint8_t touch) { uint8_t read_buf[2]; - read_buf[0] = this->read_byte_(coordinate); - read_buf[1] = this->read_byte_(coordinate + 1); + read_buf[0] = this->read_byte_(FT63X6_ADDR_TOUCH1_X + (touch * 6)); + read_buf[1] = this->read_byte_(FT63X6_ADDR_TOUCH1_X + 1 + (touch * 6)); return ((read_buf[0] & 0x0f) << 8) | read_buf[1]; } -uint8_t FT63X6Touchscreen::read_touch_id_(uint8_t id_address) { return this->read_byte_(id_address) >> 4; } +uint16_t FT63X6Touchscreen::read_touch_y_(uint8_t touch) { + uint8_t read_buf[2]; + read_buf[0] = this->read_byte_(FT63X6_ADDR_TOUCH1_Y + (touch * 6)); + read_buf[1] = this->read_byte_(FT63X6_ADDR_TOUCH1_Y + 1 + (touch * 6)); + return ((read_buf[0] & 0x0f) << 8) | read_buf[1]; +} +uint8_t FT63X6Touchscreen::read_touch_event_(uint8_t touch) { + return this->read_byte_(FT63X6_ADDR_TOUCH1_X + (touch * 6)) >> 6; +} +uint8_t FT63X6Touchscreen::read_touch_id_(uint8_t touch) { + return this->read_byte_(FT63X6_ADDR_TOUCH1_ID + (touch * 6)) >> 4; +} +uint8_t FT63X6Touchscreen::read_touch_weight_(uint8_t touch) { + return this->read_byte_(FT63X6_ADDR_TOUCH1_WEIGHT + (touch * 6)); +} +uint8_t FT63X6Touchscreen::read_touch_misc_(uint8_t touch) { + return this->read_byte_(FT63X6_ADDR_TOUCH1_MISC + (touch * 6)) >> 4; +} uint8_t FT63X6Touchscreen::read_byte_(uint8_t addr) { uint8_t byte = 0; diff --git a/esphome/components/ft63x6/ft63x6.h b/esphome/components/ft63x6/ft63x6.h index 79b1991041..8000894294 100644 --- a/esphome/components/ft63x6/ft63x6.h +++ b/esphome/components/ft63x6/ft63x6.h @@ -16,6 +16,8 @@ namespace ft63x6 { using namespace touchscreen; +static const uint8_t FT6X36_DEFAULT_THRESHOLD = 22; + class FT63X6Touchscreen : public Touchscreen, public i2c::I2CDevice { public: void setup() override; @@ -23,18 +25,26 @@ class FT63X6Touchscreen : public Touchscreen, public i2c::I2CDevice { void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } + void set_threshold(uint8_t threshold) { this->threshold_ = threshold; } protected: void hard_reset_(); - uint8_t read_byte_(uint8_t addr); void update_touches() override; InternalGPIOPin *interrupt_pin_{nullptr}; GPIOPin *reset_pin_{nullptr}; + uint8_t threshold_{FT6X36_DEFAULT_THRESHOLD}; - uint8_t read_touch_count_(); - uint16_t read_touch_coordinate_(uint8_t coordinate); - uint8_t read_touch_id_(uint8_t id_address); + uint8_t read_touch_number_(); + + uint16_t read_touch_x_(uint8_t touch); + uint16_t read_touch_y_(uint8_t touch); + uint8_t read_touch_event_(uint8_t touch); + uint8_t read_touch_id_(uint8_t touch); + uint8_t read_touch_weight_(uint8_t touch); + uint8_t read_touch_misc_(uint8_t touch); + + uint8_t read_byte_(uint8_t addr); }; } // namespace ft63x6 diff --git a/esphome/components/ft63x6/touchscreen.py b/esphome/components/ft63x6/touchscreen.py index d77d9ca287..95fa371433 100644 --- a/esphome/components/ft63x6/touchscreen.py +++ b/esphome/components/ft63x6/touchscreen.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import i2c, touchscreen -from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN +from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN, CONF_THRESHOLD CODEOWNERS = ["@gpambrozio"] DEPENDENCIES = ["i2c"] @@ -26,6 +26,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( pins.internal_gpio_input_pin_schema ), cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_THRESHOLD): cv.uint8_t, } ).extend(i2c.i2c_device_schema(0x38)) ) diff --git a/esphome/components/graph/__init__.py b/esphome/components/graph/__init__.py index 046f59ca1a..0b83b71fe4 100644 --- a/esphome/components/graph/__init__.py +++ b/esphome/components/graph/__init__.py @@ -61,6 +61,7 @@ VALUE_POSITION_TYPE = { "BELOW": ValuePositionType.VALUE_POSITION_TYPE_BELOW, } +CONF_CONTINUOUS = "continuous" GRAPH_TRACE_SCHEMA = cv.Schema( { @@ -70,6 +71,7 @@ GRAPH_TRACE_SCHEMA = cv.Schema( cv.Optional(CONF_LINE_THICKNESS): cv.positive_int, cv.Optional(CONF_LINE_TYPE): cv.enum(LINE_TYPE, upper=True), cv.Optional(CONF_COLOR): cv.use_id(color.ColorStruct), + cv.Optional(CONF_CONTINUOUS): cv.boolean, } ) @@ -186,6 +188,8 @@ async def to_code(config): if CONF_COLOR in trace: c = await cg.get_variable(trace[CONF_COLOR]) cg.add(tr.set_line_color(c)) + if CONF_CONTINUOUS in trace: + cg.add(tr.set_continuous(trace[CONF_CONTINUOUS])) cg.add(var.add_trace(tr)) # Add legend if CONF_LEGEND in config: diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index 294e16dbb1..0e437a3425 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -165,17 +165,42 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo for (auto *trace : traces_) { Color c = trace->get_line_color(); uint16_t thick = trace->get_line_thickness(); + bool continuous = trace->get_continuous(); + bool has_prev = false; + bool prev_b = false; + int16_t prev_y = 0; for (uint32_t i = 0; i < this->width_; i++) { float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange; if (!std::isnan(v) && (thick > 0)) { - int16_t x = this->width_ - 1 - i; - uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; - if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { - int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2; - for (uint16_t t = 0; t < thick; t++) { - buff->draw_pixel_at(x_offset + x, y_offset + y + t, c); + int16_t x = this->width_ - 1 - i + x_offset; + uint8_t bit = 1 << ((i % (thick * LineType::PATTERN_LENGTH)) / thick); + bool b = (trace->get_line_type() & bit) == bit; + if (b) { + int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset; + if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) { + for (uint16_t t = 0; t < thick; t++) { + buff->draw_pixel_at(x, y + t, c); + } + } else { + int16_t mid_y = (y + prev_y + thick) / 2; + if (y > prev_y) { + for (uint16_t t = prev_y + thick; t <= mid_y; t++) + buff->draw_pixel_at(x + 1, t, c); + for (uint16_t t = mid_y + 1; t < y + thick; t++) + buff->draw_pixel_at(x, t, c); + } else { + for (uint16_t t = prev_y - 1; t >= mid_y; t--) + buff->draw_pixel_at(x + 1, t, c); + for (uint16_t t = mid_y - 1; t >= y; t--) + buff->draw_pixel_at(x, t, c); + } } + prev_y = y; } + prev_b = b; + has_prev = true; + } else { + has_prev = false; } } } diff --git a/esphome/components/graph/graph.h b/esphome/components/graph/graph.h index 339a6f6d94..34accb7d3a 100644 --- a/esphome/components/graph/graph.h +++ b/esphome/components/graph/graph.h @@ -116,6 +116,8 @@ class GraphTrace { void set_line_type(enum LineType val) { this->line_type_ = val; } Color get_line_color() { return this->line_color_; } void set_line_color(Color val) { this->line_color_ = val; } + bool get_continuous() { return this->continuous_; } + void set_continuous(bool continuous) { this->continuous_ = continuous; } std::string get_name() { return name_; } const HistoryData *get_tracedata() { return &data_; } @@ -125,6 +127,7 @@ class GraphTrace { uint8_t line_thickness_{3}; enum LineType line_type_ { LINE_TYPE_SOLID }; Color line_color_{COLOR_ON}; + bool continuous_{false}; HistoryData data_; friend Graph; diff --git a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp index 84854d5b0d..99dba66c22 100644 --- a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp +++ b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp @@ -14,6 +14,7 @@ static const uint8_t GET_TOUCHES[2] = {0x81, 0x4F}; static const uint8_t GET_SWITCHES[2] = {0x80, 0x4D}; static const uint8_t GET_MAX_VALUES[2] = {0x80, 0x48}; static const size_t MAX_TOUCHES = 5; // max number of possible touches reported +static const size_t MAX_BUTTONS = 4; // max number of buttons scanned #define ERROR_CHECK(err) \ if ((err) != i2c::ERROR_OK) { \ @@ -47,9 +48,13 @@ void GT911Touchscreen::setup() { if (err == i2c::ERROR_OK) { err = this->read(data, sizeof(data)); if (err == i2c::ERROR_OK) { - this->x_raw_max_ = encode_uint16(data[1], data[0]); - this->y_raw_max_ = encode_uint16(data[3], data[2]); - esph_log_d(TAG, "Read max_x/max_y %d/%d", this->x_raw_max_, this->y_raw_max_); + if (this->x_raw_max_ == this->x_raw_min_) { + this->x_raw_max_ = encode_uint16(data[1], data[0]); + } + if (this->y_raw_max_ == this->y_raw_min_) { + this->y_raw_max_ = encode_uint16(data[3], data[2]); + } + esph_log_d(TAG, "calibration max_x/max_y %d/%d", this->x_raw_max_, this->y_raw_max_); } } } @@ -79,9 +84,6 @@ void GT911Touchscreen::update_touches() { return; } - if (num_of_touches == 0) - return; - err = this->write(GET_TOUCHES, sizeof(GET_TOUCHES), false); ERROR_CHECK(err); // num_of_touches is guaranteed to be 0..5. Also read the key data @@ -94,10 +96,13 @@ void GT911Touchscreen::update_touches() { uint16_t y = encode_uint16(data[i][4], data[i][3]); this->add_raw_touch_position_(id, x, y); } - auto keys = data[num_of_touches][0]; - for (size_t i = 0; i != 4; i++) { - for (auto *listener : this->button_listeners_) - listener->update_button(i, (keys & (1 << i)) != 0); + auto keys = data[num_of_touches][0] & ((1 << MAX_BUTTONS) - 1); + if (keys != this->button_state_) { + this->button_state_ = keys; + for (size_t i = 0; i != MAX_BUTTONS; i++) { + for (auto *listener : this->button_listeners_) + listener->update_button(i, (keys & (1 << i)) != 0); + } } } diff --git a/esphome/components/gt911/touchscreen/gt911_touchscreen.h b/esphome/components/gt911/touchscreen/gt911_touchscreen.h index 44875de5f1..a9e1279ed3 100644 --- a/esphome/components/gt911/touchscreen/gt911_touchscreen.h +++ b/esphome/components/gt911/touchscreen/gt911_touchscreen.h @@ -26,6 +26,7 @@ class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice InternalGPIOPin *interrupt_pin_{}; std::vector button_listeners_; + uint8_t button_state_{0xFF}; // last button state. Initial FF guarantees first update. }; } // namespace gt911 diff --git a/esphome/components/haier/binary_sensor/__init__.py b/esphome/components/haier/binary_sensor/__init__.py new file mode 100644 index 0000000000..4f72560a7b --- /dev/null +++ b/esphome/components/haier/binary_sensor/__init__.py @@ -0,0 +1,70 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_FAN, + ICON_RADIATOR, +) +from ..climate import ( + CONF_HAIER_ID, + HonClimate, +) + +BinarySensorTypeEnum = HonClimate.enum("SubBinarySensorType", True) + +# Haier sensors +CONF_OUTDOOR_FAN_STATUS = "outdoor_fan_status" +CONF_DEFROST_STATUS = "defrost_status" +CONF_COMPRESSOR_STATUS = "compressor_status" +CONF_INDOOR_FAN_STATUS = "indoor_fan_status" +CONF_FOUR_WAY_VALVE_STATUS = "four_way_valve_status" +CONF_INDOOR_ELECTRIC_HEATING_STATUS = "indoor_electric_heating_status" + +# Additional icons +ICON_SNOWFLAKE_THERMOMETER = "mdi:snowflake-thermometer" +ICON_HVAC = "mdi:hvac" +ICON_VALVE = "mdi:valve" + +SENSOR_TYPES = { + CONF_OUTDOOR_FAN_STATUS: binary_sensor.binary_sensor_schema( + icon=ICON_FAN, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_DEFROST_STATUS: binary_sensor.binary_sensor_schema( + icon=ICON_SNOWFLAKE_THERMOMETER, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_COMPRESSOR_STATUS: binary_sensor.binary_sensor_schema( + icon=ICON_HVAC, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_INDOOR_FAN_STATUS: binary_sensor.binary_sensor_schema( + icon=ICON_FAN, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_FOUR_WAY_VALVE_STATUS: binary_sensor.binary_sensor_schema( + icon=ICON_VALVE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_INDOOR_ELECTRIC_HEATING_STATUS: binary_sensor.binary_sensor_schema( + icon=ICON_RADIATOR, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), +} + +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate), + } +).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()}) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_HAIER_ID]) + + for type, _ in SENSOR_TYPES.items(): + if conf := config.get(type): + sens = await binary_sensor.new_binary_sensor(conf) + binary_sensor_type = getattr(BinarySensorTypeEnum, type.upper()) + cg.add(paren.set_sub_binary_sensor(binary_sensor_type, sens)) diff --git a/esphome/components/haier/climate.py b/esphome/components/haier/climate.py index 49d42a231f..a700be8be2 100644 --- a/esphome/components/haier/climate.py +++ b/esphome/components/haier/climate.py @@ -2,7 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv import esphome.final_validate as fv -from esphome.components import uart, sensor, climate, logger +from esphome.components import uart, climate, logger from esphome import automation from esphome.const import ( CONF_BEEPER, @@ -18,12 +18,9 @@ from esphome.const import ( CONF_SUPPORTED_SWING_MODES, CONF_TARGET_TEMPERATURE, CONF_TEMPERATURE_STEP, + CONF_TRIGGER_ID, CONF_VISUAL, CONF_WIFI, - DEVICE_CLASS_TEMPERATURE, - ICON_THERMOMETER, - STATE_CLASS_MEASUREMENT, - UNIT_CELSIUS, ) from esphome.components.climate import ( ClimateMode, @@ -41,7 +38,6 @@ PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5 PROTOCOL_CONTROL_PACKET_SIZE = 10 CODEOWNERS = ["@paveldn"] -AUTO_LOAD = ["sensor"] DEPENDENCIES = ["climate", "uart"] CONF_ALTERNATIVE_SWING_CONTROL = "alternative_swing_control" CONF_ANSWER_TIMEOUT = "answer_timeout" @@ -49,13 +45,14 @@ CONF_CONTROL_METHOD = "control_method" CONF_CONTROL_PACKET_SIZE = "control_packet_size" CONF_DISPLAY = "display" CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow" +CONF_ON_ALARM_START = "on_alarm_start" +CONF_ON_ALARM_END = "on_alarm_end" CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" CONF_VERTICAL_AIRFLOW = "vertical_airflow" CONF_WIFI_SIGNAL = "wifi_signal" PROTOCOL_HON = "HON" PROTOCOL_SMARTAIR2 = "SMARTAIR2" -PROTOCOLS_SUPPORTED = [PROTOCOL_HON, PROTOCOL_SMARTAIR2] haier_ns = cg.esphome_ns.namespace("haier") HaierClimateBase = haier_ns.class_( @@ -64,6 +61,7 @@ HaierClimateBase = haier_ns.class_( HonClimate = haier_ns.class_("HonClimate", HaierClimateBase) Smartair2Climate = haier_ns.class_("Smartair2Climate", HaierClimateBase) +CONF_HAIER_ID = "haier_id" AirflowVerticalDirection = haier_ns.enum("AirflowVerticalDirection", True) AIRFLOW_VERTICAL_DIRECTION_OPTIONS = { @@ -85,8 +83,8 @@ AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS = { } SUPPORTED_SWING_MODES_OPTIONS = { - "OFF": ClimateSwingMode.CLIMATE_SWING_OFF, # always available - "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, # always available + "OFF": ClimateSwingMode.CLIMATE_SWING_OFF, + "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, } @@ -101,13 +99,15 @@ SUPPORTED_CLIMATE_MODES_OPTIONS = { } SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS = { + "AWAY": ClimatePreset.CLIMATE_PRESET_AWAY, "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, "COMFORT": ClimatePreset.CLIMATE_PRESET_COMFORT, } SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = { - "ECO": ClimatePreset.CLIMATE_PRESET_ECO, + "AWAY": ClimatePreset.CLIMATE_PRESET_AWAY, "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, + "ECO": ClimatePreset.CLIMATE_PRESET_ECO, "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, } @@ -118,6 +118,16 @@ SUPPORTED_HON_CONTROL_METHODS = { "SET_SINGLE_PARAMETER": HonControlMethod.SET_SINGLE_PARAMETER, } +HaierAlarmStartTrigger = haier_ns.class_( + "HaierAlarmStartTrigger", + automation.Trigger.template(cg.uint8, cg.const_char_ptr), +) + +HaierAlarmEndTrigger = haier_ns.class_( + "HaierAlarmEndTrigger", + automation.Trigger.template(cg.uint8, cg.const_char_ptr), +) + def validate_visual(config): if CONF_VISUAL in config: @@ -200,9 +210,7 @@ CONFIG_SCHEMA = cv.All( ): cv.boolean, cv.Optional( CONF_SUPPORTED_PRESETS, - default=list( - SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS.keys() - ), + default=list(["BOOST", "COMFORT"]), # No AWAY by default ): cv.ensure_list( cv.enum(SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS, upper=True) ), @@ -222,16 +230,26 @@ CONFIG_SCHEMA = cv.All( ): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50), cv.Optional( CONF_SUPPORTED_PRESETS, - default=list(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS.keys()), + default=list(["BOOST", "ECO", "SLEEP"]), # No AWAY by default ): cv.ensure_list( cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True) ), - cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_THERMOMETER, - accuracy_decimals=0, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, + cv.Optional(CONF_OUTDOOR_TEMPERATURE): cv.invalid( + f"The {CONF_OUTDOOR_TEMPERATURE} option is deprecated, use a sensor for a haier platform instead" + ), + cv.Optional(CONF_ON_ALARM_START): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + HaierAlarmStartTrigger + ), + } + ), + cv.Optional(CONF_ON_ALARM_END): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + HaierAlarmEndTrigger + ), + } ), } ), @@ -436,9 +454,6 @@ async def to_code(config): cg.add(var.set_beeper_state(config[CONF_BEEPER])) if CONF_DISPLAY in config: cg.add(var.set_display_state(config[CONF_DISPLAY])) - if CONF_OUTDOOR_TEMPERATURE in config: - sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE]) - cg.add(var.set_outdoor_temperature_sensor(sens)) if CONF_SUPPORTED_MODES in config: cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) if CONF_SUPPORTED_SWING_MODES in config: @@ -457,5 +472,15 @@ async def to_code(config): config[CONF_CONTROL_PACKET_SIZE] - PROTOCOL_CONTROL_PACKET_SIZE ) ) + for conf in config.get(CONF_ON_ALARM_START, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf + ) + for conf in config.get(CONF_ON_ALARM_END, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf + ) # https://github.com/paveldn/HaierProtocol - cg.add_library("pavlodn/HaierProtocol", "0.9.24") + cg.add_library("pavlodn/HaierProtocol", "0.9.25") diff --git a/esphome/components/haier/haier_base.cpp b/esphome/components/haier/haier_base.cpp index 6943fc7d9c..a3f68bb081 100644 --- a/esphome/components/haier/haier_base.cpp +++ b/esphome/components/haier/haier_base.cpp @@ -25,13 +25,14 @@ const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) { "SENDING_INIT_1", "SENDING_INIT_2", "SENDING_FIRST_STATUS_REQUEST", - "SENDING_ALARM_STATUS_REQUEST", + "SENDING_FIRST_ALARM_STATUS_REQUEST", "IDLE", "SENDING_STATUS_REQUEST", "SENDING_UPDATE_SIGNAL_REQUEST", "SENDING_SIGNAL_LEVEL", "SENDING_CONTROL", "SENDING_ACTION_COMMAND", + "SENDING_ALARM_STATUS_REQUEST", "UNKNOWN" // Should be the last! }; static_assert( diff --git a/esphome/components/haier/haier_base.h b/esphome/components/haier/haier_base.h index 75abbc20fb..504c841e5f 100644 --- a/esphome/components/haier/haier_base.h +++ b/esphome/components/haier/haier_base.h @@ -64,7 +64,7 @@ class HaierClimateBase : public esphome::Component, SENDING_INIT_1 = 0, SENDING_INIT_2, SENDING_FIRST_STATUS_REQUEST, - SENDING_ALARM_STATUS_REQUEST, + SENDING_FIRST_ALARM_STATUS_REQUEST, // FUNCTIONAL STATE IDLE, SENDING_STATUS_REQUEST, @@ -72,6 +72,7 @@ class HaierClimateBase : public esphome::Component, SENDING_SIGNAL_LEVEL, SENDING_CONTROL, SENDING_ACTION_COMMAND, + SENDING_ALARM_STATUS_REQUEST, NUM_PROTOCOL_PHASES }; const char *phase_to_string_(ProtocolPhases phase); diff --git a/esphome/components/haier/hon_climate.cpp b/esphome/components/haier/hon_climate.cpp index 09f90fffa8..9933cb4c8f 100644 --- a/esphome/components/haier/hon_climate.cpp +++ b/esphome/components/haier/hon_climate.cpp @@ -2,6 +2,7 @@ #include #include "esphome/components/climate/climate.h" #include "esphome/components/uart/uart.h" +#include "esphome/core/helpers.h" #include "hon_climate.h" #include "hon_packet.h" @@ -16,6 +17,7 @@ constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET = -64; constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5; constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500); +constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000; hon_protocol::VerticalSwingMode get_vertical_swing_mode(AirflowVerticalDirection direction) { switch (direction) { @@ -50,10 +52,9 @@ hon_protocol::HorizontalSwingMode get_horizontal_swing_mode(AirflowHorizontalDir } HonClimate::HonClimate() - : cleaning_status_(CleaningState::NO_CLEANING), - got_valid_outdoor_temp_(false), - active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - outdoor_sensor_(nullptr) { + : cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00} { last_status_message_ = std::unique_ptr(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]); this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID; this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO; @@ -65,8 +66,6 @@ void HonClimate::set_beeper_state(bool state) { this->beeper_status_ = state; } bool HonClimate::get_beeper_state() const { return this->beeper_status_; } -void HonClimate::set_outdoor_temperature_sensor(esphome::sensor::Sensor *sensor) { this->outdoor_sensor_ = sensor; } - AirflowVerticalDirection HonClimate::get_vertical_airflow() const { return this->vertical_direction_; }; void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) { @@ -110,6 +109,14 @@ void HonClimate::start_steri_cleaning() { } } +void HonClimate::add_alarm_start_callback(std::function &&callback) { + this->alarm_start_callback_.add(std::move(callback)); +} + +void HonClimate::add_alarm_end_callback(std::function &&callback) { + this->alarm_end_callback_.add(std::move(callback)); +} + haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size) { @@ -194,7 +201,7 @@ haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameTy switch (this->protocol_phase_) { case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: ESP_LOGI(TAG, "First HVAC status received"); - this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); + this->set_phase(ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST); break; case ProtocolPhases::SENDING_ACTION_COMMAND: // Do nothing, phase will be changed in process_phase @@ -251,12 +258,15 @@ haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_ this->set_phase(ProtocolPhases::IDLE); return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; } - if (this->protocol_phase_ != ProtocolPhases::SENDING_ALARM_STATUS_REQUEST) { + if ((this->protocol_phase_ != ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST) && + (this->protocol_phase_ != ProtocolPhases::SENDING_ALARM_STATUS_REQUEST)) { // Don't expect this answer now this->set_phase(ProtocolPhases::IDLE); return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; } - memcpy(this->active_alarms_, data + 2, 8); + if (data_size < sizeof(active_alarms_) + 2) + return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; + this->process_alarm_message_(data, data_size, this->protocol_phase_ >= ProtocolPhases::IDLE); this->set_phase(ProtocolPhases::IDLE); return haier_protocol::HandlerError::HANDLER_OK; } else { @@ -265,6 +275,19 @@ haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_ } } +haier_protocol::HandlerError HonClimate::alarm_status_message_handler_(haier_protocol::FrameType type, + const uint8_t *buffer, size_t size) { + haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK; + if (size < sizeof(this->active_alarms_) + 2) { + // Log error but confirm anyway to avoid to many messages + result = haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; + } + this->process_alarm_message_(buffer, size, true); + this->haier_protocol_.send_answer(haier_protocol::HaierMessage(haier_protocol::FrameType::CONFIRM)); + this->last_alarm_request_ = std::chrono::steady_clock::now(); + return result; +} + void HonClimate::set_handlers() { // Set handlers this->haier_protocol_.set_answer_handler( @@ -291,6 +314,10 @@ void HonClimate::set_handlers() { haier_protocol::FrameType::REPORT_NETWORK_STATUS, std::bind(&HonClimate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + this->haier_protocol_.set_message_handler( + haier_protocol::FrameType::ALARM_STATUS, + std::bind(&HonClimate::alarm_status_message_handler_, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3)); } void HonClimate::dump_config() { @@ -339,7 +366,14 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { static const haier_protocol::HaierMessage STATUS_REQUEST( haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA); - this->send_message_(STATUS_REQUEST, this->use_crc_); + static const haier_protocol::HaierMessage BIG_DATA_REQUEST( + haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_BIG_DATA); + if ((this->protocol_phase_ == ProtocolPhases::SENDING_FIRST_STATUS_REQUEST) || + (!this->should_get_big_data_())) { + this->send_message_(STATUS_REQUEST, this->use_crc_); + } else { + this->send_message_(BIG_DATA_REQUEST, this->use_crc_); + } this->last_status_request_ = now; } break; @@ -363,10 +397,12 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { this->set_phase(ProtocolPhases::IDLE); break; #endif + case ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST: case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST(haier_protocol::FrameType::GET_ALARM_STATUS); this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_); + this->last_alarm_request_ = now; } break; case ProtocolPhases::SENDING_CONTROL: @@ -417,12 +453,16 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST); this->forced_request_status_ = false; + } else if (std::chrono::duration_cast(now - this->last_alarm_request_).count() > + ALARM_STATUS_REQUEST_INTERVAL_MS) { + this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); } #ifdef USE_WIFI else if (this->send_wifi_signal_ && (std::chrono::duration_cast(now - this->last_signal_request_).count() > - SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) + SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) { this->set_phase(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST); + } #endif } break; default: @@ -452,6 +492,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; + control_out_buffer[4] = 0; // This byte should be cleared before setting values bool has_hvac_settings = false; if (this->current_hvac_settings_.valid) { has_hvac_settings = true; @@ -552,31 +593,41 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { out_data->quiet_mode = 0; out_data->fast_mode = 0; out_data->sleep_mode = 0; + out_data->ten_degree = 0; break; case CLIMATE_PRESET_ECO: // Eco is not supported in Fan only mode out_data->quiet_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0; out_data->fast_mode = 0; out_data->sleep_mode = 0; + out_data->ten_degree = 0; break; case CLIMATE_PRESET_BOOST: out_data->quiet_mode = 0; // Boost is not supported in Fan only mode out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0; out_data->sleep_mode = 0; + out_data->ten_degree = 0; break; case CLIMATE_PRESET_AWAY: out_data->quiet_mode = 0; out_data->fast_mode = 0; out_data->sleep_mode = 0; + // 10 degrees allowed only in heat mode + out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0; break; case CLIMATE_PRESET_SLEEP: out_data->quiet_mode = 0; out_data->fast_mode = 0; out_data->sleep_mode = 1; + out_data->ten_degree = 0; break; default: ESP_LOGE("Control", "Unsupported preset"); + out_data->quiet_mode = 0; + out_data->fast_mode = 0; + out_data->sleep_mode = 0; + out_data->ten_degree = 0; break; } } @@ -595,9 +646,131 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); } +void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) { + constexpr size_t active_alarms_size = sizeof(this->active_alarms_); + if (size >= active_alarms_size + 2) { + if (check_new) { + size_t alarm_code = 0; + for (int i = active_alarms_size - 1; i >= 0; i--) { + if (packet[2 + i] != active_alarms_[i]) { + uint8_t alarm_bit = 1; + for (int b = 0; b < 8; b++) { + if ((packet[2 + i] & alarm_bit) != (this->active_alarms_[i] & alarm_bit)) { + bool alarm_status = (packet[2 + i] & alarm_bit) != 0; + int log_level = alarm_status ? ESPHOME_LOG_LEVEL_WARN : ESPHOME_LOG_LEVEL_INFO; + const char *alarm_message = alarm_code < esphome::haier::hon_protocol::HON_ALARM_COUNT + ? esphome::haier::hon_protocol::HON_ALARM_MESSAGES[alarm_code].c_str() + : "Unknown"; + esp_log_printf_(log_level, TAG, __LINE__, "Alarm %s (%d): %s", alarm_status ? "activated" : "deactivated", + alarm_code, alarm_message); + if (alarm_status) { + this->alarm_start_callback_.call(alarm_code, alarm_message); + this->active_alarm_count_ += 1.0f; + } else { + this->alarm_end_callback_.call(alarm_code, alarm_message); + this->active_alarm_count_ -= 1.0f; + } + } + alarm_bit <<= 1; + alarm_code++; + } + active_alarms_[i] = packet[2 + i]; + } else + alarm_code += 8; + } + } else { + float alarm_count = 0.0f; + static uint8_t nibble_bits_count[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; + for (size_t i = 0; i < sizeof(this->active_alarms_); i++) { + alarm_count += (float) (nibble_bits_count[packet[2 + i] & 0x0F] + nibble_bits_count[packet[2 + i] >> 4]); + } + this->active_alarm_count_ = alarm_count; + memcpy(this->active_alarms_, packet + 2, sizeof(this->active_alarms_)); + } + } +} + +#ifdef USE_SENSOR +void HonClimate::set_sub_sensor(SubSensorType type, sensor::Sensor *sens) { + if (type < SubSensorType::SUB_SENSOR_TYPE_COUNT) { + if (type >= SubSensorType::BIG_DATA_FRAME_SUB_SENSORS) { + if ((this->sub_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) { + this->big_data_sensors_--; + } else if ((this->sub_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) { + this->big_data_sensors_++; + } + } + this->sub_sensors_[(size_t) type] = sens; + } +} + +void HonClimate::update_sub_sensor_(SubSensorType type, float value) { + if (type < SubSensorType::SUB_SENSOR_TYPE_COUNT) { + size_t index = (size_t) type; + if ((this->sub_sensors_[index] != nullptr) && + ((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->raw_state != value))) + this->sub_sensors_[index]->publish_state(value); + } +} +#endif // USE_SENSOR + +#ifdef USE_BINARY_SENSOR +void HonClimate::set_sub_binary_sensor(SubBinarySensorType type, binary_sensor::BinarySensor *sens) { + if (type < SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT) { + if ((this->sub_binary_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) { + this->big_data_sensors_--; + } else if ((this->sub_binary_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) { + this->big_data_sensors_++; + } + this->sub_binary_sensors_[(size_t) type] = sens; + } +} + +void HonClimate::update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value) { + if (value < 2) { + bool converted_value = value == 1; + size_t index = (size_t) type; + if ((this->sub_binary_sensors_[index] != nullptr) && ((!this->sub_binary_sensors_[index]->has_state()) || + (this->sub_binary_sensors_[index]->state != converted_value))) + this->sub_binary_sensors_[index]->publish_state(converted_value); + } +} +#endif // USE_BINARY_SENSOR + haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) { - if (size < hon_protocol::HAIER_STATUS_FRAME_SIZE + this->extra_control_packet_bytes_) + size_t expected_size = 2 + sizeof(hon_protocol::HaierPacketControl) + sizeof(hon_protocol::HaierPacketSensors) + + this->extra_control_packet_bytes_; + if (size < expected_size) return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; + uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1]; + if ((subtype == 0x7D01) && (size >= expected_size + 4 + sizeof(hon_protocol::HaierPacketBigData))) { + // Got BigData packet + const hon_protocol::HaierPacketBigData *bd_packet = + (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size + 4]); +#ifdef USE_SENSOR + this->update_sub_sensor_(SubSensorType::INDOOR_COIL_TEMPERATURE, bd_packet->indoor_coil_temperature / 2.0 - 20); + this->update_sub_sensor_(SubSensorType::OUTDOOR_COIL_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64); + this->update_sub_sensor_(SubSensorType::OUTDOOR_DEFROST_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64); + this->update_sub_sensor_(SubSensorType::OUTDOOR_IN_AIR_TEMPERATURE, bd_packet->outdoor_in_air_temperature - 64); + this->update_sub_sensor_(SubSensorType::OUTDOOR_OUT_AIR_TEMPERATURE, bd_packet->outdoor_out_air_temperature - 64); + this->update_sub_sensor_(SubSensorType::POWER, encode_uint16(bd_packet->power[0], bd_packet->power[1])); + this->update_sub_sensor_(SubSensorType::COMPRESSOR_FREQUENCY, bd_packet->compressor_frequency); + this->update_sub_sensor_(SubSensorType::COMPRESSOR_CURRENT, + encode_uint16(bd_packet->compressor_current[0], bd_packet->compressor_current[1]) / 10.0); + this->update_sub_sensor_( + SubSensorType::EXPANSION_VALVE_OPEN_DEGREE, + encode_uint16(bd_packet->expansion_valve_open_degree[0], bd_packet->expansion_valve_open_degree[1]) / 4095.0); +#endif // USE_SENSOR +#ifdef USE_BINARY_SENSOR + this->update_sub_binary_sensor_(SubBinarySensorType::OUTDOOR_FAN_STATUS, bd_packet->outdoor_fan_status); + this->update_sub_binary_sensor_(SubBinarySensorType::DEFROST_STATUS, bd_packet->defrost_status); + this->update_sub_binary_sensor_(SubBinarySensorType::COMPRESSOR_STATUS, bd_packet->compressor_status); + this->update_sub_binary_sensor_(SubBinarySensorType::INDOOR_FAN_STATUS, bd_packet->indoor_fan_status); + this->update_sub_binary_sensor_(SubBinarySensorType::FOUR_WAY_VALVE_STATUS, bd_packet->four_way_valve_status); + this->update_sub_binary_sensor_(SubBinarySensorType::INDOOR_ELECTRIC_HEATING_STATUS, + bd_packet->indoor_electric_heating_status); +#endif // USE_BINARY_SENSOR + } struct { hon_protocol::HaierPacketControl control; hon_protocol::HaierPacketSensors sensors; @@ -609,13 +782,17 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * if (packet.sensors.error_status != 0) { ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status); } - if ((this->outdoor_sensor_ != nullptr) && +#ifdef USE_SENSOR + if ((this->sub_sensors_[(size_t) SubSensorType::OUTDOOR_TEMPERATURE] != nullptr) && (this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) { this->got_valid_outdoor_temp_ = true; - float otemp = (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET); - if ((!this->outdoor_sensor_->has_state()) || (this->outdoor_sensor_->get_raw_state() != otemp)) - this->outdoor_sensor_->publish_state(otemp); + this->update_sub_sensor_(SubSensorType::OUTDOOR_TEMPERATURE, + (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET)); } + if ((this->sub_sensors_[(size_t) SubSensorType::HUMIDITY] != nullptr) && (packet.sensors.room_humidity <= 100)) { + this->update_sub_sensor_(SubSensorType::HUMIDITY, (float) packet.sensors.room_humidity); + } +#endif // USE_SENSOR bool should_publish = false; { // Extra modes/presets @@ -626,6 +803,8 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * this->preset = CLIMATE_PRESET_BOOST; } else if (packet.control.sleep_mode != 0) { this->preset = CLIMATE_PRESET_SLEEP; + } else if (packet.control.ten_degree != 0) { + this->preset = CLIMATE_PRESET_AWAY; } else { this->preset = CLIMATE_PRESET_NONE; } @@ -882,48 +1061,66 @@ void HonClimate::fill_control_messages_queue_() { // CLimate preset { uint8_t fast_mode_buf[] = {0x00, 0xFF}; + uint8_t away_mode_buf[] = {0x00, 0xFF}; if (!new_power) { // If AC is off - no presets allowed quiet_mode_buf[1] = 0x00; fast_mode_buf[1] = 0x00; + away_mode_buf[1] = 0x00; } else if (climate_control.preset.has_value()) { switch (climate_control.preset.value()) { case CLIMATE_PRESET_NONE: quiet_mode_buf[1] = 0x00; fast_mode_buf[1] = 0x00; + away_mode_buf[1] = 0x00; break; case CLIMATE_PRESET_ECO: // Eco is not supported in Fan only mode quiet_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00; fast_mode_buf[1] = 0x00; + away_mode_buf[1] = 0x00; break; case CLIMATE_PRESET_BOOST: quiet_mode_buf[1] = 0x00; // Boost is not supported in Fan only mode fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00; + away_mode_buf[1] = 0x00; + break; + case CLIMATE_PRESET_AWAY: + quiet_mode_buf[1] = 0x00; + fast_mode_buf[1] = 0x00; + away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00; break; default: ESP_LOGE("Control", "Unsupported preset"); break; } } - if (quiet_mode_buf[1] != 0xFF) { + auto presets = this->traits_.get_supported_presets(); + if ((quiet_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_ECO) != presets.end()))) { this->control_messages_queue_.push( haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + (uint8_t) hon_protocol::DataParameters::QUIET_MODE, quiet_mode_buf, 2)); } - if (fast_mode_buf[1] != 0xFF) { + if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) { this->control_messages_queue_.push( haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + (uint8_t) hon_protocol::DataParameters::FAST_MODE, fast_mode_buf, 2)); } + if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) { + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::TEN_DEGREE, + away_mode_buf, 2)); + } } // Target temperature - if (climate_control.target_temperature.has_value()) { + if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) { uint8_t buffer[2] = {0x00, 0x00}; buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16; this->control_messages_queue_.push( @@ -1010,12 +1207,24 @@ bool HonClimate::prepare_pending_action() { void HonClimate::process_protocol_reset() { HaierClimateBase::process_protocol_reset(); - if (this->outdoor_sensor_ != nullptr) { - this->outdoor_sensor_->publish_state(NAN); +#ifdef USE_SENSOR + for (auto &sub_sensor : this->sub_sensors_) { + if ((sub_sensor != nullptr) && sub_sensor->has_state()) + sub_sensor->publish_state(NAN); } +#endif // USE_SENSOR this->got_valid_outdoor_temp_ = false; this->hvac_hardware_info_.reset(); } +bool HonClimate::should_get_big_data_() { + if (this->big_data_sensors_ > 0) { + static uint8_t counter = 0; + counter = (counter + 1) % 3; + return counter == 1; + } + return false; +} + } // namespace haier } // namespace esphome diff --git a/esphome/components/haier/hon_climate.h b/esphome/components/haier/hon_climate.h index 1ba6a8e041..c4fae20a98 100644 --- a/esphome/components/haier/hon_climate.h +++ b/esphome/components/haier/hon_climate.h @@ -1,7 +1,13 @@ #pragma once #include +#ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#include "esphome/core/automation.h" #include "haier_base.h" namespace esphome { @@ -33,6 +39,48 @@ enum class CleaningState : uint8_t { enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER }; class HonClimate : public HaierClimateBase { +#ifdef USE_SENSOR + public: + enum class SubSensorType { + // Used data based sensors + OUTDOOR_TEMPERATURE = 0, + HUMIDITY, + // Big data based sensors + INDOOR_COIL_TEMPERATURE, + OUTDOOR_COIL_TEMPERATURE, + OUTDOOR_DEFROST_TEMPERATURE, + OUTDOOR_IN_AIR_TEMPERATURE, + OUTDOOR_OUT_AIR_TEMPERATURE, + POWER, + COMPRESSOR_FREQUENCY, + COMPRESSOR_CURRENT, + EXPANSION_VALVE_OPEN_DEGREE, + SUB_SENSOR_TYPE_COUNT, + BIG_DATA_FRAME_SUB_SENSORS = INDOOR_COIL_TEMPERATURE, + }; + void set_sub_sensor(SubSensorType type, sensor::Sensor *sens); + + protected: + void update_sub_sensor_(SubSensorType type, float value); + sensor::Sensor *sub_sensors_[(size_t) SubSensorType::SUB_SENSOR_TYPE_COUNT]{nullptr}; +#endif +#ifdef USE_BINARY_SENSOR + public: + enum class SubBinarySensorType { + OUTDOOR_FAN_STATUS = 0, + DEFROST_STATUS, + COMPRESSOR_STATUS, + INDOOR_FAN_STATUS, + FOUR_WAY_VALVE_STATUS, + INDOOR_ELECTRIC_HEATING_STATUS, + SUB_BINARY_SENSOR_TYPE_COUNT, + }; + void set_sub_binary_sensor(SubBinarySensorType type, binary_sensor::BinarySensor *sens); + + protected: + void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value); + binary_sensor::BinarySensor *sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]{nullptr}; +#endif public: HonClimate(); HonClimate(const HonClimate &) = delete; @@ -41,7 +89,6 @@ class HonClimate : public HaierClimateBase { void dump_config() override; void set_beeper_state(bool state); bool get_beeper_state() const; - void set_outdoor_temperature_sensor(esphome::sensor::Sensor *sensor); AirflowVerticalDirection get_vertical_airflow() const; void set_vertical_airflow(AirflowVerticalDirection direction); AirflowHorizontalDirection get_horizontal_airflow() const; @@ -52,6 +99,9 @@ class HonClimate : public HaierClimateBase { void start_steri_cleaning(); void set_extra_control_packet_bytes_size(size_t size) { this->extra_control_packet_bytes_ = size; }; void set_control_method(HonControlMethod method) { this->control_method_ = method; }; + void add_alarm_start_callback(std::function &&callback); + void add_alarm_end_callback(std::function &&callback); + float get_active_alarm_count() const { return this->active_alarm_count_; } protected: void set_handlers() override; @@ -60,6 +110,7 @@ class HonClimate : public HaierClimateBase { haier_protocol::HaierMessage get_power_message(bool state) override; bool prepare_pending_action() override; void process_protocol_reset() override; + bool should_get_big_data_(); // Answers handlers haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type, @@ -77,8 +128,11 @@ class HonClimate : public HaierClimateBase { haier_protocol::HandlerError get_alarm_status_answer_handler_(haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size); + haier_protocol::HandlerError alarm_status_message_handler_(haier_protocol::FrameType type, const uint8_t *buffer, + size_t size); // Helper functions haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); + void process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new); void fill_control_messages_queue_(); void clear_control_messages_queue_(); @@ -99,8 +153,28 @@ class HonClimate : public HaierClimateBase { uint8_t active_alarms_[8]; int extra_control_packet_bytes_; HonControlMethod control_method_; - esphome::sensor::Sensor *outdoor_sensor_; std::queue control_messages_queue_; + CallbackManager alarm_start_callback_{}; + CallbackManager alarm_end_callback_{}; + float active_alarm_count_{NAN}; + std::chrono::steady_clock::time_point last_alarm_request_; + int big_data_sensors_{0}; +}; + +class HaierAlarmStartTrigger : public Trigger { + public: + explicit HaierAlarmStartTrigger(HonClimate *parent) { + parent->add_alarm_start_callback( + [this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); }); + } +}; + +class HaierAlarmEndTrigger : public Trigger { + public: + explicit HaierAlarmEndTrigger(HonClimate *parent) { + parent->add_alarm_end_callback( + [this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); }); + } }; } // namespace haier diff --git a/esphome/components/haier/hon_packet.h b/esphome/components/haier/hon_packet.h index 7724b43854..bbca7bb653 100644 --- a/esphome/components/haier/hon_packet.h +++ b/esphome/components/haier/hon_packet.h @@ -55,18 +55,18 @@ enum class FanMode : uint8_t { FAN_HIGH = 0x01, FAN_MID = 0x02, FAN_LOW = 0x03, struct HaierPacketControl { // Control bytes starts here - // 10 + // 1 uint8_t set_point; // Target temperature with 16°C offset (0x00 = 16°C) - // 11 + // 2 uint8_t vertical_swing_mode : 4; // See enum VerticalSwingMode uint8_t : 0; - // 12 + // 3 uint8_t fan_mode : 3; // See enum FanMode uint8_t special_mode : 2; // See enum SpecialMode uint8_t ac_mode : 3; // See enum ConditioningMode - // 13 + // 4 uint8_t : 8; - // 14 + // 5 uint8_t ten_degree : 1; // 10 degree status uint8_t display_status : 1; // If 0 disables AC's display uint8_t half_degree : 1; // Use half degree @@ -75,7 +75,7 @@ struct HaierPacketControl { uint8_t use_fahrenheit : 1; // Use Fahrenheit instead of Celsius uint8_t : 1; uint8_t steri_clean : 1; - // 15 + // 6 uint8_t ac_power : 1; // Is ac on or off uint8_t health_mode : 1; // Health mode (negative ions) on or off uint8_t electric_heating_status : 1; // Electric heating status @@ -84,16 +84,16 @@ struct HaierPacketControl { uint8_t sleep_mode : 1; // Sleep mode uint8_t lock_remote : 1; // Disable remote uint8_t beeper_status : 1; // If 1 disables AC's command feedback beeper (need to be set on every control command) - // 16 + // 7 uint8_t target_humidity; // Target humidity (0=30% .. 3C=90%, step = 1%) - // 17 + // 8 uint8_t horizontal_swing_mode : 3; // See enum HorizontalSwingMode uint8_t : 3; uint8_t human_sensing_status : 2; // Human sensing status - // 18 + // 9 uint8_t change_filter : 1; // Filter need replacement uint8_t : 0; - // 19 + // 10 uint8_t fresh_air_status : 1; // Fresh air status uint8_t humidification_status : 1; // Humidification status uint8_t pm2p5_cleaning_status : 1; // PM2.5 cleaning status @@ -105,40 +105,68 @@ struct HaierPacketControl { }; struct HaierPacketSensors { - // 20 + // 11 uint8_t room_temperature; // 0.5°C step - // 21 + // 12 uint8_t room_humidity; // 0%-100% with 1% step - // 22 + // 13 uint8_t outdoor_temperature; // 1°C step, -64°C offset (0=-64°C) - // 23 + // 14 uint8_t pm2p5_level : 2; // Indoor PM2.5 grade (00: Excellent, 01: good, 02: Medium, 03: Bad) uint8_t air_quality : 2; // Air quality grade (00: Excellent, 01: good, 02: Medium, 03: Bad) uint8_t human_sensing : 2; // Human presence result (00: N/A, 01: not detected, 02: One, 03: Multiple) uint8_t : 1; uint8_t ac_type : 1; // 00 - Heat and cool, 01 - Cool only) - // 24 + // 15 uint8_t error_status; // See enum ErrorStatus - // 25 + // 16 uint8_t operation_source : 2; // who is controlling AC (00: Other, 01: Remote control, 02: Button, 03: ESP) uint8_t operation_mode_hk : 2; // Homekit only, operation mode (00: Cool, 01: Dry, 02: Heat, 03: Fan) uint8_t : 3; uint8_t err_confirmation : 1; // If 1 clear error status - // 26 + // 17 uint16_t total_cleaning_time; // Cleaning cumulative time (1h step) - // 28 + // 19 uint16_t indoor_pm2p5_value; // Indoor PM2.5 value (0 ug/m3 - 4095 ug/m3, 1 ug/m3 step) - // 30 + // 21 uint16_t outdoor_pm2p5_value; // Outdoor PM2.5 value (0 ug/m3 - 4095 ug/m3, 1 ug/m3 step) - // 32 + // 23 uint16_t ch2o_value; // Formaldehyde value (0 ug/m3 - 10000 ug/m3, 1 ug/m3 step) - // 34 + // 25 uint16_t voc_value; // VOC value (Volatile Organic Compounds) (0 ug/m3 - 1023 ug/m3, 1 ug/m3 step) - // 36 + // 27 uint16_t co2_value; // CO2 value (0 PPM - 10000 PPM, 1 PPM step) }; -constexpr size_t HAIER_STATUS_FRAME_SIZE = 2 + sizeof(HaierPacketControl) + sizeof(HaierPacketSensors); +struct HaierPacketBigData { + // 29 + uint8_t power[2]; // AC power consumption (0W - 65535W, 1W step) + // 31 + uint8_t indoor_coil_temperature; // 0.5°C step, -20°C offset (0=-20°C) + // 32 + uint8_t outdoor_out_air_temperature; // 1°C step, -64°C offset (0=-64°C) + // 33 + uint8_t outdoor_coil_temperature; // 1°C step, -64°C offset (0=-64°C) + // 34 + uint8_t outdoor_in_air_temperature; // 1°C step, -64°C offset (0=-64°C) + // 35 + uint8_t outdoor_defrost_temperature; // 1°C step, -64°C offset (0=-64°C) + // 36 + uint8_t compressor_frequency; // 1Hz step, 0Hz - 127Hz + // 37 + uint8_t compressor_current[2]; // 0.1A step, 0.0A - 51.1A (0x0000 - 0x01FF) + // 39 + uint8_t outdoor_fan_status : 2; // 0 - off, 1 - on, 2 - information not available + uint8_t defrost_status : 2; // 0 - off, 1 - on, 2 - information not available + uint8_t : 0; + // 40 + uint8_t compressor_status : 2; // 0 - off, 1 - on, 2 - information not available + uint8_t indoor_fan_status : 2; // 0 - off, 1 - on, 2 - information not available + uint8_t four_way_valve_status : 2; // 0 - off, 1 - on, 2 - information not available + uint8_t indoor_electric_heating_status : 2; // 0 - off, 1 - on, 2 - information not available + // 41 + uint8_t expansion_valve_open_degree[2]; // 0 - 4095 +}; struct DeviceVersionAnswer { char protocol_version[8]; @@ -163,6 +191,62 @@ enum class SubcommandsControl : uint16_t { // content: all values like in status packet) }; +const std::string HON_ALARM_MESSAGES[] = { + "Outdoor module failure", + "Outdoor defrost sensor failure", + "Outdoor compressor exhaust sensor failure", + "Outdoor EEPROM abnormality", + "Indoor coil sensor failure", + "Indoor-outdoor communication failure", + "Power supply overvoltage protection", + "Communication failure between panel and indoor unit", + "Outdoor compressor overheat protection", + "Outdoor environmental sensor abnormality", + "Full water protection", + "Indoor EEPROM failure", + "Outdoor out air sensor failure", + "CBD and module communication failure", + "Indoor DC fan failure", + "Outdoor DC fan failure", + "Door switch failure", + "Dust filter needs cleaning reminder", + "Water shortage protection", + "Humidity sensor failure", + "Indoor temperature sensor failure", + "Manipulator limit failure", + "Indoor PM2.5 sensor failure", + "Outdoor PM2.5 sensor failure", + "Indoor heating overload/high load alarm", + "Outdoor AC current protection", + "Outdoor compressor operation abnormality", + "Outdoor DC current protection", + "Outdoor no-load failure", + "CT current abnormality", + "Indoor cooling freeze protection", + "High and low pressure protection", + "Compressor out air temperature is too high", + "Outdoor evaporator sensor failure", + "Outdoor cooling overload", + "Water pump drainage failure", + "Three-phase power supply failure", + "Four-way valve failure", + "External alarm/scraper flow switch failure", + "Temperature cutoff protection alarm", + "Different mode operation failure", + "Electronic expansion valve failure", + "Dual heat source sensor Tw failure", + "Communication failure with the wired controller", + "Indoor unit address duplication failure", + "50Hz zero crossing failure", + "Outdoor unit failure", + "Formaldehyde sensor failure", + "VOC sensor failure", + "CO2 sensor failure", + "Firewall failure", +}; + +constexpr size_t HON_ALARM_COUNT = sizeof(HON_ALARM_MESSAGES) / sizeof(HON_ALARM_MESSAGES[0]); + } // namespace hon_protocol } // namespace haier } // namespace esphome diff --git a/esphome/components/haier/sensor/__init__.py b/esphome/components/haier/sensor/__init__.py new file mode 100644 index 0000000000..9a4965493d --- /dev/null +++ b/esphome/components/haier/sensor/__init__.py @@ -0,0 +1,151 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_POWER, + CONF_HUMIDITY, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_FREQUENCY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_CURRENT_AC, + ICON_FLASH, + ICON_GAUGE, + ICON_HEATING_COIL, + ICON_PULSE, + ICON_THERMOMETER, + ICON_WATER_PERCENT, + ICON_WEATHER_WINDY, + STATE_CLASS_MEASUREMENT, + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_HERTZ, + UNIT_PERCENT, + UNIT_WATT, +) +from ..climate import ( + CONF_HAIER_ID, + HonClimate, +) + +SensorTypeEnum = HonClimate.enum("SubSensorType", True) + +# Haier sensors +CONF_COMPRESSOR_CURRENT = "compressor_current" +CONF_COMPRESSOR_FREQUENCY = "compressor_frequency" +CONF_EXPANSION_VALVE_OPEN_DEGREE = "expansion_valve_open_degree" +CONF_INDOOR_COIL_TEMPERATURE = "indoor_coil_temperature" +CONF_OUTDOOR_COIL_TEMPERATURE = "outdoor_coil_temperature" +CONF_OUTDOOR_DEFROST_TEMPERATURE = "outdoor_defrost_temperature" +CONF_OUTDOOR_IN_AIR_TEMPERATURE = "outdoor_in_air_temperature" +CONF_OUTDOOR_OUT_AIR_TEMPERATURE = "outdoor_out_air_temperature" +CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" + +# Additional icons +ICON_SNOWFLAKE_THERMOMETER = "mdi:snowflake-thermometer" + +SENSOR_TYPES = { + CONF_COMPRESSOR_CURRENT: sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + icon=ICON_CURRENT_AC, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_COMPRESSOR_FREQUENCY: sensor.sensor_schema( + unit_of_measurement=UNIT_HERTZ, + icon=ICON_PULSE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_FREQUENCY, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_EXPANSION_VALVE_OPEN_DEGREE: sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_GAUGE, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_HUMIDITY: sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + CONF_INDOOR_COIL_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_HEATING_COIL, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_OUTDOOR_COIL_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_HEATING_COIL, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_OUTDOOR_DEFROST_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_SNOWFLAKE_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_OUTDOOR_IN_AIR_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_WEATHER_WINDY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_OUTDOOR_OUT_AIR_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_WEATHER_WINDY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_OUTDOOR_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + CONF_POWER: sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + icon=ICON_FLASH, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), +} + +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate), + } +).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()}) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_HAIER_ID]) + + for type, _ in SENSOR_TYPES.items(): + if conf := config.get(type): + sens = await sensor.new_sensor(conf) + sensor_type = getattr(SensorTypeEnum, type.upper()) + cg.add(paren.set_sub_sensor(sensor_type, sens)) diff --git a/esphome/components/haier/smartair2_climate.cpp b/esphome/components/haier/smartair2_climate.cpp index c2326883f7..00590694d5 100644 --- a/esphome/components/haier/smartair2_climate.cpp +++ b/esphome/components/haier/smartair2_climate.cpp @@ -95,7 +95,7 @@ haier_protocol::HandlerError Smartair2Climate::messages_timeout_handler_with_cyc ESP_LOGI(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) message_type, phase_to_string_(this->protocol_phase_)); ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1); - if (new_phase >= ProtocolPhases::SENDING_ALARM_STATUS_REQUEST) + if (new_phase >= ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST) new_phase = ProtocolPhases::SENDING_INIT_1; this->set_phase(new_phase); return haier_protocol::HandlerError::HANDLER_OK; @@ -170,9 +170,12 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); break; - case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: + case ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST: this->set_phase(ProtocolPhases::SENDING_INIT_1); break; + case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: + this->set_phase(ProtocolPhases::IDLE); + break; case ProtocolPhases::SENDING_CONTROL: if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) { ESP_LOGI(TAG, "Sending control packet"); @@ -343,19 +346,29 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() { } else if (climate_control.preset.has_value()) { switch (climate_control.preset.value()) { case CLIMATE_PRESET_NONE: + out_data->ten_degree = 0; out_data->turbo_mode = 0; out_data->quiet_mode = 0; break; case CLIMATE_PRESET_BOOST: + out_data->ten_degree = 0; out_data->turbo_mode = 1; out_data->quiet_mode = 0; break; case CLIMATE_PRESET_COMFORT: + out_data->ten_degree = 0; out_data->turbo_mode = 0; out_data->quiet_mode = 1; break; + case CLIMATE_PRESET_AWAY: + // Only allowed in heat mode + out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0; + out_data->turbo_mode = 0; + out_data->quiet_mode = 0; + break; default: ESP_LOGE("Control", "Unsupported preset"); + out_data->ten_degree = 0; out_data->turbo_mode = 0; out_data->quiet_mode = 0; break; @@ -381,6 +394,8 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin this->preset = CLIMATE_PRESET_BOOST; } else if (packet.control.quiet_mode != 0) { this->preset = CLIMATE_PRESET_COMFORT; + } else if (packet.control.ten_degree != 0) { + this->preset = CLIMATE_PRESET_AWAY; } else { this->preset = CLIMATE_PRESET_NONE; } diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index a043b4a61b..8af4ca590f 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -119,4 +119,4 @@ def to_code(config): cg.add_library("tonia/HeatpumpIR", "1.0.23") if CORE.is_esp8266 or CORE.is_esp32: - cg.add_library("crankyoldgit/IRremoteESP8266", "2.7.12") + cg.add_library("crankyoldgit/IRremoteESP8266", "2.8.4") diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index 1a9f47faaf..14e83f60e1 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -96,7 +96,7 @@ void HLW8012Component::update() { this->energy_sensor_->publish_state(energy); } - if (this->change_mode_at_++ == this->change_mode_every_) { + if (this->change_mode_every_ != 0 && this->change_mode_at_++ == this->change_mode_every_) { this->current_mode_ = !this->current_mode_; ESP_LOGV(TAG, "Changing mode to %s mode", this->current_mode_ ? "CURRENT" : "VOLTAGE"); this->change_mode_at_ = 0; diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index 033cccc3d4..2687edaca2 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -79,8 +79,9 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float, cv.Optional(CONF_MODEL, default="HLW8012"): cv.enum(MODELS, upper=True), - cv.Optional(CONF_CHANGE_MODE_EVERY, default=8): cv.All( - cv.uint32_t, cv.Range(min=1) + cv.Optional(CONF_CHANGE_MODE_EVERY, default=8): cv.Any( + "never", + cv.All(cv.uint32_t, cv.Range(min=1)), ), cv.Optional(CONF_INITIAL_MODE, default=CONF_VOLTAGE): cv.one_of( *INITIAL_MODES, lower=True @@ -114,6 +115,10 @@ async def to_code(config): cg.add(var.set_energy_sensor(sens)) cg.add(var.set_current_resistor(config[CONF_CURRENT_RESISTOR])) cg.add(var.set_voltage_divider(config[CONF_VOLTAGE_DIVIDER])) - cg.add(var.set_change_mode_every(config[CONF_CHANGE_MODE_EVERY])) cg.add(var.set_initial_mode(INITIAL_MODES[config[CONF_INITIAL_MODE]])) cg.add(var.set_sensor_model(config[CONF_MODEL])) + + interval = config[CONF_CHANGE_MODE_EVERY] + if interval == "never": + interval = 0 + cg.add(var.set_change_mode_every(interval)) diff --git a/esphome/components/honeywell_hih_i2c/__init__.py b/esphome/components/honeywell_hih_i2c/__init__.py new file mode 100644 index 0000000000..8d13fcb152 --- /dev/null +++ b/esphome/components/honeywell_hih_i2c/__init__.py @@ -0,0 +1,3 @@ +"""Support for Honeywell HumidIcon HIH""" + +CODEOWNERS = ["@Benichou34"] diff --git a/esphome/components/honeywell_hih_i2c/honeywell_hih.cpp b/esphome/components/honeywell_hih_i2c/honeywell_hih.cpp new file mode 100644 index 0000000000..64d5ddb541 --- /dev/null +++ b/esphome/components/honeywell_hih_i2c/honeywell_hih.cpp @@ -0,0 +1,97 @@ +// Honeywell HumidIcon I2C Sensors +// https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/humidity-with-temperature-sensors/common/documents/sps-siot-i2c-comms-humidicon-tn-009061-2-en-ciid-142171.pdf +// + +#include "honeywell_hih.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace honeywell_hih_i2c { + +static const char *const TAG = "honeywell_hih.i2c"; + +static const uint8_t REQUEST_CMD[1] = {0x00}; // Measurement Request Format +static const uint16_t MAX_COUNT = 0x3FFE; // 2^14 - 2 + +void HoneywellHIComponent::read_sensor_data_() { + uint8_t data[4]; + + if (this->read(data, sizeof(data)) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Communication with Honeywell HIH failed!"); + this->mark_failed(); + return; + } + + const uint16_t raw_humidity = (static_cast(data[0] & 0x3F) << 8) | data[1]; + float humidity = (static_cast(raw_humidity) / MAX_COUNT) * 100; + + const uint16_t raw_temperature = (static_cast(data[2]) << 6) | (data[3] >> 2); + float temperature = (static_cast(raw_temperature) / MAX_COUNT) * 165 - 40; + + ESP_LOGD(TAG, "Got temperature=%.2f°C humidity=%.2f%%", temperature, humidity); + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + if (this->humidity_sensor_ != nullptr) + this->humidity_sensor_->publish_state(humidity); +} + +void HoneywellHIComponent::start_measurement_() { + if (this->write(REQUEST_CMD, sizeof(REQUEST_CMD)) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Communication with Honeywell HIH failed!"); + this->mark_failed(); + return; + } + + this->measurement_running_ = true; +} + +bool HoneywellHIComponent::is_measurement_ready_() { + uint8_t data[1]; + + if (this->read(data, sizeof(data)) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Communication with Honeywell HIH failed!"); + this->mark_failed(); + return false; + } + + // Check status bits + return ((data[0] & 0xC0) == 0x00); +} + +void HoneywellHIComponent::measurement_timeout_() { + ESP_LOGE(TAG, "Honeywell HIH Timeout!"); + this->measurement_running_ = false; + this->mark_failed(); +} + +void HoneywellHIComponent::update() { + ESP_LOGV(TAG, "Update Honeywell HIH Sensor"); + + this->start_measurement_(); + // The measurement cycle duration is typically 36.65 ms for temperature and humidity readings. + this->set_timeout("meas_timeout", 100, [this] { this->measurement_timeout_(); }); +} + +void HoneywellHIComponent::loop() { + if (this->measurement_running_ && this->is_measurement_ready_()) { + this->measurement_running_ = false; + this->cancel_timeout("meas_timeout"); + this->read_sensor_data_(); + } +} + +void HoneywellHIComponent::dump_config() { + ESP_LOGD(TAG, "Honeywell HIH:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with Honeywell HIH failed!"); + } + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); + LOG_UPDATE_INTERVAL(this); +} + +float HoneywellHIComponent::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace honeywell_hih_i2c +} // namespace esphome diff --git a/esphome/components/honeywell_hih_i2c/honeywell_hih.h b/esphome/components/honeywell_hih_i2c/honeywell_hih.h new file mode 100644 index 0000000000..4457eab1da --- /dev/null +++ b/esphome/components/honeywell_hih_i2c/honeywell_hih.h @@ -0,0 +1,34 @@ +// Honeywell HumidIcon I2C Sensors +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace honeywell_hih_i2c { + +class HoneywellHIComponent : public PollingComponent, public i2c::I2CDevice { + public: + void dump_config() override; + float get_setup_priority() const override; + void loop() override; + void update() override; + + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } + void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } + + protected: + bool measurement_running_{false}; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + + private: + void read_sensor_data_(); + void start_measurement_(); + bool is_measurement_ready_(); + void measurement_timeout_(); +}; + +} // namespace honeywell_hih_i2c +} // namespace esphome diff --git a/esphome/components/honeywell_hih_i2c/sensor.py b/esphome/components/honeywell_hih_i2c/sensor.py new file mode 100644 index 0000000000..f5a6ad2398 --- /dev/null +++ b/esphome/components/honeywell_hih_i2c/sensor.py @@ -0,0 +1,56 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, +) + +DEPENDENCIES = ["i2c"] + +honeywell_hih_ns = cg.esphome_ns.namespace("honeywell_hih_i2c") +HONEYWELLHIComponent = honeywell_hih_ns.class_( + "HoneywellHIComponent", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HONEYWELLHIComponent), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x27)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity_sensor(sens)) diff --git a/esphome/components/honeywellabp2_i2c/__init__.py b/esphome/components/honeywellabp2_i2c/__init__.py index e748df3c98..29a910eca9 100644 --- a/esphome/components/honeywellabp2_i2c/__init__.py +++ b/esphome/components/honeywellabp2_i2c/__init__.py @@ -1,2 +1,3 @@ """Support for Honeywell ABP2""" + CODEOWNERS = ["@jpfaff"] diff --git a/esphome/components/host/__init__.py b/esphome/components/host/__init__.py index 14d2597866..3bd3b6b172 100644 --- a/esphome/components/host/__init__.py +++ b/esphome/components/host/__init__.py @@ -4,8 +4,10 @@ from esphome.const import ( KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, PLATFORM_HOST, + CONF_MAC_ADDRESS, ) from esphome.core import CORE +from esphome.helpers import IS_MACOS import esphome.config_validation as cv import esphome.codegen as cg @@ -14,7 +16,6 @@ from .const import KEY_HOST # force import gpio to register pin schema from .gpio import host_pin_to_code # noqa - CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["network"] @@ -28,12 +29,21 @@ def set_core_data(config): CONFIG_SCHEMA = cv.All( - cv.Schema({}), + cv.Schema( + { + cv.Optional(CONF_MAC_ADDRESS, default="98:35:69:ab:f6:79"): cv.mac_address, + } + ), set_core_data, ) async def to_code(config): cg.add_build_flag("-DUSE_HOST") + cg.add_define("USE_ESPHOME_HOST_MAC_ADDRESS", config[CONF_MAC_ADDRESS].parts) + cg.add_build_flag("-std=c++17") + cg.add_build_flag("-lsodium") + if IS_MACOS: + cg.add_build_flag("-L/opt/homebrew/lib") cg.add_define("ESPHOME_BOARD", "host") cg.add_platformio_option("platform", "platformio/native") diff --git a/esphome/components/host/preferences.cpp b/esphome/components/host/preferences.cpp index bf45893e40..7b939cdebb 100644 --- a/esphome/components/host/preferences.cpp +++ b/esphome/components/host/preferences.cpp @@ -1,36 +1,87 @@ #ifdef USE_HOST +#include +#include #include "preferences.h" -#include -#include "esphome/core/preferences.h" -#include "esphome/core/helpers.h" -#include "esphome/core/log.h" -#include "esphome/core/defines.h" +#include "esphome/core/application.h" namespace esphome { namespace host { +namespace fs = std::filesystem; static const char *const TAG = "host.preferences"; -class HostPreferences : public ESPPreferences { - public: - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { return {}; } +void HostPreferences::setup_() { + if (this->setup_complete_) + return; + this->filename_.append(getenv("HOME")); + this->filename_.append("/.esphome"); + this->filename_.append("/prefs"); + fs::create_directories(this->filename_); + this->filename_.append("/"); + this->filename_.append(App.get_name()); + this->filename_.append(".prefs"); + FILE *fp = fopen(this->filename_.c_str(), "rb"); + if (fp != nullptr) { + while (!feof((fp))) { + uint32_t key; + uint8_t len; + if (fread(&key, sizeof(key), 1, fp) != 1) + break; + if (fread(&len, sizeof(len), 1, fp) != 1) + break; + uint8_t data[len]; + if (fread(data, sizeof(uint8_t), len, fp) != len) + break; + std::vector vec(data, data + len); + this->data[key] = vec; + } + fclose(fp); + } + this->setup_complete_ = true; +} - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { return {}; } +bool HostPreferences::sync() { + this->setup_(); + FILE *fp = fopen(this->filename_.c_str(), "wb"); + std::map>::iterator it; - bool sync() override { return true; } - bool reset() override { return true; } + for (it = this->data.begin(); it != this->data.end(); ++it) { + fwrite(&it->first, sizeof(uint32_t), 1, fp); + uint8_t len = it->second.size(); + fwrite(&len, sizeof(len), 1, fp); + fwrite(it->second.data(), sizeof(uint8_t), it->second.size(), fp); + } + fclose(fp); + return true; +} + +bool HostPreferences::reset() { + host_preferences->data.clear(); + return true; +} + +ESPPreferenceObject HostPreferences::make_preference(size_t length, uint32_t type, bool in_flash) { + auto backend = new HostPreferenceBackend(type); + return ESPPreferenceObject(backend); }; void setup_preferences() { auto *pref = new HostPreferences(); // NOLINT(cppcoreguidelines-owning-memory) + host_preferences = pref; global_preferences = pref; } +bool HostPreferenceBackend::save(const uint8_t *data, size_t len) { + return host_preferences->save(this->key_, data, len); +} + +bool HostPreferenceBackend::load(uint8_t *data, size_t len) { return host_preferences->load(this->key_, data, len); } + +HostPreferences *host_preferences; } // namespace host ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome #endif // USE_HOST diff --git a/esphome/components/host/preferences.h b/esphome/components/host/preferences.h index 7462360ec3..6707366517 100644 --- a/esphome/components/host/preferences.h +++ b/esphome/components/host/preferences.h @@ -2,10 +2,63 @@ #ifdef USE_HOST +#include "esphome/core/preferences.h" +#include + namespace esphome { namespace host { +class HostPreferenceBackend : public ESPPreferenceBackend { + public: + explicit HostPreferenceBackend(uint32_t key) { this->key_ = key; } + + bool save(const uint8_t *data, size_t len) override; + bool load(uint8_t *data, size_t len) override; + + protected: + uint32_t key_{}; +}; + +class HostPreferences : public ESPPreferences { + public: + bool sync() override; + bool reset() override; + + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override; + ESPPreferenceObject make_preference(size_t length, uint32_t type) override { + return make_preference(length, type, false); + } + + bool save(uint32_t key, const uint8_t *data, size_t len) { + if (len > 255) + return false; + this->setup_(); + std::vector vec(data, data + len); + this->data[key] = vec; + return true; + } + + bool load(uint32_t key, uint8_t *data, size_t len) { + if (len > 255) + return false; + this->setup_(); + if (this->data.count(key) == 0) + return false; + auto vec = this->data[key]; + if (vec.size() != len) + return false; + memcpy(data, vec.data(), len); + return true; + } + + protected: + void setup_(); + bool setup_complete_{}; + std::string filename_{}; + std::map> data{}; +}; void setup_preferences(); +extern HostPreferences *host_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace host } // namespace esphome diff --git a/esphome/components/htu21d/htu21d.cpp b/esphome/components/htu21d/htu21d.cpp index a8133ae32e..d0dbb15a43 100644 --- a/esphome/components/htu21d/htu21d.cpp +++ b/esphome/components/htu21d/htu21d.cpp @@ -39,45 +39,54 @@ void HTU21DComponent::dump_config() { LOG_SENSOR(" ", "Humidity", this->humidity_); } void HTU21DComponent::update() { - uint16_t raw_temperature; if (this->write(&HTU21D_REGISTER_TEMPERATURE, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } - delay(50); // NOLINT - if (this->read(reinterpret_cast(&raw_temperature), 2) != i2c::ERROR_OK) { - this->status_set_warning(); - return; - } - raw_temperature = i2c::i2ctohs(raw_temperature); - float temperature = (float(raw_temperature & 0xFFFC)) * 175.72f / 65536.0f - 46.85f; + // According to the datasheet sht21 temperature readings can take up to 85ms + this->set_timeout(85, [this]() { + uint16_t raw_temperature; + if (this->read(reinterpret_cast(&raw_temperature), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_temperature = i2c::i2ctohs(raw_temperature); - uint16_t raw_humidity; - if (this->write(&HTU21D_REGISTER_HUMIDITY, 1) != i2c::ERROR_OK) { - this->status_set_warning(); - return; - } - delay(50); // NOLINT - if (this->read(reinterpret_cast(&raw_humidity), 2) != i2c::ERROR_OK) { - this->status_set_warning(); - return; - } - raw_humidity = i2c::i2ctohs(raw_humidity); + float temperature = (float(raw_temperature & 0xFFFC)) * 175.72f / 65536.0f - 46.85f; - float humidity = (float(raw_humidity & 0xFFFC)) * 125.0f / 65536.0f - 6.0f; + ESP_LOGD(TAG, "Got Temperature=%.1f°C", temperature); - int8_t heater_level = this->get_heater_level(); + if (this->temperature_ != nullptr) + this->temperature_->publish_state(temperature); + this->status_clear_warning(); - ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%% Heater Level=%d", temperature, humidity, heater_level); + if (this->write(&HTU21D_REGISTER_HUMIDITY, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } - if (this->temperature_ != nullptr) - this->temperature_->publish_state(temperature); - if (this->humidity_ != nullptr) - this->humidity_->publish_state(humidity); - if (this->heater_ != nullptr) - this->heater_->publish_state(heater_level); - this->status_clear_warning(); + this->set_timeout(50, [this]() { + uint16_t raw_humidity; + if (this->read(reinterpret_cast(&raw_humidity), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_humidity = i2c::i2ctohs(raw_humidity); + + float humidity = (float(raw_humidity & 0xFFFC)) * 125.0f / 65536.0f - 6.0f; + + int8_t heater_level = this->get_heater_level(); + + ESP_LOGD(TAG, "Got Humidity=%.1f%% Heater Level=%d", humidity, heater_level); + + if (this->humidity_ != nullptr) + this->humidity_->publish_state(humidity); + if (this->heater_ != nullptr) + this->heater_->publish_state(heater_level); + this->status_clear_warning(); + }); + }); } bool HTU21DComponent::is_heater_enabled() { diff --git a/esphome/components/htu31d/__init__.py b/esphome/components/htu31d/__init__.py new file mode 100644 index 0000000000..039693cb30 --- /dev/null +++ b/esphome/components/htu31d/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@betterengineering"] diff --git a/esphome/components/htu31d/htu31d.cpp b/esphome/components/htu31d/htu31d.cpp new file mode 100644 index 0000000000..928250a5b2 --- /dev/null +++ b/esphome/components/htu31d/htu31d.cpp @@ -0,0 +1,269 @@ +/* + * This file contains source code derived from Adafruit_HTU31D which is under + * the BSD license: + * Written by Limor Fried/Ladyada for Adafruit Industries. + * BSD license, all text above must be included in any redistribution. + * + * Modifications made by Mark Spicer. + */ + +#include "htu31d.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace htu31d { + +/** Logging prefix */ +static const char *const TAG = "htu31d"; + +/** Default I2C address for the HTU31D. */ +static const uint8_t HTU31D_DEFAULT_I2CADDR = 0x40; + +/** Read temperature and humidity. */ +static const uint8_t HTU31D_READTEMPHUM = 0x00; + +/** Start a conversion! */ +static const uint8_t HTU31D_CONVERSION = 0x40; + +/** Read serial number command. */ +static const uint8_t HTU31D_READSERIAL = 0x0A; + +/** Enable heater */ +static const uint8_t HTU31D_HEATERON = 0x04; + +/** Disable heater */ +static const uint8_t HTU31D_HEATEROFF = 0x02; + +/** Reset command. */ +static const uint8_t HTU31D_RESET = 0x1E; + +/** Diagnostics command. */ +static const uint8_t HTU31D_DIAGNOSTICS = 0x08; + +/** + * Computes a CRC result for the provided input. + * + * @returns the computed CRC result for the provided input + */ +uint8_t compute_crc(uint32_t value) { + uint32_t polynom = 0x98800000; // x^8 + x^5 + x^4 + 1 + uint32_t msb = 0x80000000; + uint32_t mask = 0xFF800000; + uint32_t threshold = 0x00000080; + uint32_t result = value; + + while (msb != threshold) { + // Check if msb of current value is 1 and apply XOR mask + if (result & msb) + result = ((result ^ polynom) & mask) | (result & ~mask); + + // Shift by one + msb >>= 1; + mask >>= 1; + polynom >>= 1; + } + + return result; +} + +/** + * Resets the sensor and ensures that the devices serial number can be read over + * I2C. + */ +void HTU31DComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up esphome/components/htu31d HTU31D..."); + + if (!this->reset_()) { + this->mark_failed(); + return; + } + + if (this->read_serial_num_() == 0) { + this->mark_failed(); + return; + } +} + +/** + * Called once every update interval (user configured, defaults to 60s) and sets + * the current temperature and humidity. + */ +void HTU31DComponent::update() { + ESP_LOGD(TAG, "Checking temperature and humidty values"); + + // Trigger a conversion. From the spec sheet: The conversion command triggers + // a single temperature and humidity conversion. + if (this->write_register(HTU31D_CONVERSION, nullptr, 0) != i2c::ERROR_OK) { + this->status_set_warning(); + ESP_LOGE(TAG, "Received errror writing conversion register"); + return; + } + + // Wait conversion time. + this->set_timeout(20, [this]() { + uint8_t thdata[6]; + if (this->read_register(HTU31D_READTEMPHUM, thdata, 6) != i2c::ERROR_OK) { + this->status_set_warning(); + ESP_LOGE(TAG, "Error reading temperature/humidty register"); + return; + } + + // Calculate temperature value. + uint16_t raw_temp = encode_uint16(thdata[0], thdata[1]); + + uint8_t crc = compute_crc((uint32_t) raw_temp << 8); + if (crc != thdata[2]) { + this->status_set_warning(); + ESP_LOGE(TAG, "Error validating temperature CRC"); + return; + } + + float temperature = raw_temp; + temperature /= 65535.0f; + temperature *= 165; + temperature -= 40; + + if (this->temperature_ != nullptr) { + this->temperature_->publish_state(temperature); + } + + // Calculate humidty value. + uint16_t raw_hum = encode_uint16(thdata[3], thdata[4]); + + crc = compute_crc((uint32_t) raw_hum << 8); + if (crc != thdata[5]) { + this->status_set_warning(); + ESP_LOGE(TAG, "Error validating humidty CRC"); + return; + } + + float humidity = raw_hum; + humidity /= 65535.0f; + humidity *= 100; + + if (this->humidity_ != nullptr) { + this->humidity_->publish_state(humidity); + } + + ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity); + this->status_clear_warning(); + }); +} + +/** + * Logs the current compoenent config. + */ +void HTU31DComponent::dump_config() { + ESP_LOGCONFIG(TAG, "HTU31D:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with HTU31D failed!"); + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Humidity", this->humidity_); +} + +/** + * Sends a 'reset' request to the HTU31D, followed by a 15ms delay. + * + * @returns True if was able to write the command successfully + */ +bool HTU31DComponent::reset_() { + if (this->write_register(HTU31D_RESET, nullptr, 0) != i2c::ERROR_OK) { + return false; + } + + delay(15); + return true; +} + +/** + * Reads the serial number from the device and checks the CRC. + * + * @returns the 24bit serial number from the device + */ +uint32_t HTU31DComponent::read_serial_num_() { + uint8_t reply[4]; + uint32_t serial = 0; + uint8_t padding = 0; + + // Verify we can read the device serial. + if (this->read_register(HTU31D_READSERIAL, reply, 4) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Error reading device serial"); + return 0; + } + + serial = encode_uint32(reply[0], reply[1], reply[2], padding); + + uint8_t crc = compute_crc(serial); + if (crc != reply[3]) { + ESP_LOGE(TAG, "Error validating serial CRC"); + return 0; + } + + ESP_LOGD(TAG, "Found serial: 0x%X", serial); + + return serial; +} + +/** + * Checks the diagnostics register to determine if the heater is currently + * enabled. + * + * @returns True if the heater is currently enabled, False otherwise + */ +bool HTU31DComponent::is_heater_enabled() { + uint8_t reply[1]; + uint8_t heater_enabled_position = 0; + uint8_t mask = 1 << heater_enabled_position; + uint8_t diagnostics = 0; + + if (this->read_register(HTU31D_DIAGNOSTICS, reply, 1) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Error reading device serial"); + return false; + } + + diagnostics = reply[0]; + return (diagnostics & mask) != 0; +} + +/** + * Sets the heater state on or off. + * + * @param desired True for on, and False for off. + */ +void HTU31DComponent::set_heater_state(bool desired) { + bool current = this->is_heater_enabled(); + + // If the current state matches the desired state, there is nothing to do. + if (current == desired) { + return; + } + + // Update heater state. + esphome::i2c::ErrorCode err; + if (desired) { + err = this->write_register(HTU31D_HEATERON, nullptr, 0); + } else { + err = this->write_register(HTU31D_HEATEROFF, nullptr, 0); + } + + // Record any error. + if (err != i2c::ERROR_OK) { + this->status_set_warning(); + ESP_LOGE(TAG, "Received error updating heater state"); + return; + } +} + +/** + * Sets the startup priority for this component. + * + * @returns The startup priority + */ +float HTU31DComponent::get_setup_priority() const { return setup_priority::DATA; } +} // namespace htu31d +} // namespace esphome diff --git a/esphome/components/htu31d/htu31d.h b/esphome/components/htu31d/htu31d.h new file mode 100644 index 0000000000..9462133ced --- /dev/null +++ b/esphome/components/htu31d/htu31d.h @@ -0,0 +1,33 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace htu31d { + +class HTU31DComponent : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; /// Setup (reset) the sensor and check connection. + void update() override; /// Update the sensor values (temperature+humidity). + void dump_config() override; /// Dumps the configuration values. + + void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { this->humidity_ = humidity; } + + void set_heater_state(bool desired); + bool is_heater_enabled(); + + float get_setup_priority() const override; + + protected: + bool reset_(); + uint32_t read_serial_num_(); + + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; +}; +} // namespace htu31d +} // namespace esphome diff --git a/esphome/components/htu31d/sensor.py b/esphome/components/htu31d/sensor.py new file mode 100644 index 0000000000..fe53aa376e --- /dev/null +++ b/esphome/components/htu31d/sensor.py @@ -0,0 +1,56 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, +) + +DEPENDENCIES = ["i2c"] + +htu31d_ns = cg.esphome_ns.namespace("htu31d") +HTU31DComponent = htu31d_ns.class_( + "HTU31DComponent", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HTU31DComponent), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x40)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature(sens)) + + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity(sens)) diff --git a/esphome/components/hydreon_rgxx/__init__.py b/esphome/components/hydreon_rgxx/__init__.py index 5fe050edf2..b488bfc1b4 100644 --- a/esphome/components/hydreon_rgxx/__init__.py +++ b/esphome/components/hydreon_rgxx/__init__.py @@ -6,6 +6,7 @@ DEPENDENCIES = ["uart"] hydreon_rgxx_ns = cg.esphome_ns.namespace("hydreon_rgxx") RGModel = hydreon_rgxx_ns.enum("RGModel") +RG15Resolution = hydreon_rgxx_ns.enum("RG15Resolution") HydreonRGxxComponent = hydreon_rgxx_ns.class_( "HydreonRGxxComponent", cg.PollingComponent, uart.UARTDevice ) diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp index 58e00ba7a5..95702fe9e8 100644 --- a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp @@ -17,6 +17,17 @@ void HydreonRGxxComponent::dump_config() { if (this->is_failed()) { ESP_LOGE(TAG, "Connection with hydreon_rgxx failed!"); } + if (model_ == RG9) { + ESP_LOGCONFIG(TAG, " Model: RG9"); + ESP_LOGCONFIG(TAG, " Disable Led: %s", TRUEFALSE(this->disable_led_)); + } else { + ESP_LOGCONFIG(TAG, " Model: RG15"); + if (this->resolution_ == FORCE_HIGH) { + ESP_LOGCONFIG(TAG, " Resolution: high"); + } else { + ESP_LOGCONFIG(TAG, " Resolution: low"); + } + } LOG_UPDATE_INTERVAL(this); int i = 0; @@ -25,10 +36,6 @@ void HydreonRGxxComponent::dump_config() { LOG_SENSOR(" ", #s, this->sensors_[i - 1]); \ } HYDREON_RGXX_PROTOCOL_LIST(HYDREON_RGXX_LOG_SENSOR, ); - - if (this->model_ == RG9) { - ESP_LOGCONFIG(TAG, "disable_led: %s", TRUEFALSE(this->disable_led_)); - } } void HydreonRGxxComponent::setup() { @@ -193,7 +200,11 @@ void HydreonRGxxComponent::process_line_() { ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); if (this->model_ == RG15) { - this->write_str("P\nH\nM\n"); // set sensor to (P)polling mode, (H)high res mode, (M)metric mode + if (this->resolution_ == FORCE_HIGH) { + this->write_str("P\nH\nM\n"); // set sensor to (P)polling mode, (H)high res mode, (M)metric mode + } else { + this->write_str("P\nL\nM\n"); // set sensor to (P)polling mode, (L)low res mode, (M)metric mode + } } if (this->model_ == RG9) { diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.h b/esphome/components/hydreon_rgxx/hydreon_rgxx.h index 1edda59800..76b0985a24 100644 --- a/esphome/components/hydreon_rgxx/hydreon_rgxx.h +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.h @@ -16,6 +16,11 @@ enum RGModel { RG15 = 2, }; +enum RG15Resolution { + FORCE_LOW = 1, + FORCE_HIGH = 2, +}; + #ifdef HYDREON_RGXX_NUM_SENSORS static const uint8_t NUM_SENSORS = HYDREON_RGXX_NUM_SENSORS; #else @@ -37,6 +42,7 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { void set_em_sat_sensor(binary_sensor::BinarySensor *sensor) { this->em_sat_sensor_ = sensor; } #endif void set_model(RGModel model) { model_ = model; } + void set_resolution(RG15Resolution resolution) { resolution_ = resolution; } void set_request_temperature(bool b) { request_temperature_ = b; } /// Schedule data readings. @@ -68,7 +74,10 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { int16_t boot_count_ = 0; int16_t no_response_count_ = 0; std::string buffer_; + RGModel model_ = RG9; + RG15Resolution resolution_ = FORCE_HIGH; + int sw_version_ = 0; bool too_cold_ = false; bool lens_bad_ = false; diff --git a/esphome/components/hydreon_rgxx/sensor.py b/esphome/components/hydreon_rgxx/sensor.py index 0fc380f959..72b74bf624 100644 --- a/esphome/components/hydreon_rgxx/sensor.py +++ b/esphome/components/hydreon_rgxx/sensor.py @@ -5,6 +5,7 @@ from esphome.const import ( CONF_ID, CONF_MODEL, CONF_MOISTURE, + CONF_RESOLUTION, CONF_TEMPERATURE, DEVICE_CLASS_PRECIPITATION_INTENSITY, DEVICE_CLASS_PRECIPITATION, @@ -14,7 +15,7 @@ from esphome.const import ( ICON_THERMOMETER, ) -from . import RGModel, HydreonRGxxComponent +from . import RGModel, RG15Resolution, HydreonRGxxComponent UNIT_INTENSITY = "intensity" UNIT_MILLIMETERS = "mm" @@ -37,11 +38,18 @@ RG_MODELS = { # 1.100 - https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2021.03.11-rg-9_instructions.pdf # 1.200 - https://rainsensors.com/wp-content/uploads/sites/3/2022/03/2022.02.17-rev-1.200-rg-9_instructions.pdf } -SUPPORTED_SENSORS = { + +RG15_RESOLUTION = { + "low": RG15Resolution.FORCE_LOW, + "high": RG15Resolution.FORCE_HIGH, +} + +SUPPORTED_OPTIONS = { CONF_ACC: ["RG_15"], CONF_EVENT_ACC: ["RG_15"], CONF_TOTAL_ACC: ["RG_15"], CONF_R_INT: ["RG_15"], + CONF_RESOLUTION: ["RG_15"], CONF_MOISTURE: ["RG_9"], CONF_TEMPERATURE: ["RG_9"], CONF_DISABLE_LED: ["RG_9"], @@ -57,7 +65,7 @@ PROTOCOL_NAMES = { def _validate(config): - for conf, models in SUPPORTED_SENSORS.items(): + for conf, models in SUPPORTED_OPTIONS.items(): if conf in config: if config[CONF_MODEL] not in models: raise cv.Invalid( @@ -75,6 +83,7 @@ CONFIG_SCHEMA = cv.All( upper=True, space="_", ), + cv.Optional(CONF_RESOLUTION): cv.enum(RG15_RESOLUTION, upper=False), cv.Optional(CONF_ACC): sensor.sensor_schema( unit_of_measurement=UNIT_MILLIMETERS, accuracy_decimals=2, @@ -138,6 +147,10 @@ async def to_code(config): sens = await sensor.new_sensor(config[conf]) cg.add(var.set_sensor(sens, i)) + cg.add(var.set_model(config[CONF_MODEL])) + if CONF_RESOLUTION in config: + cg.add(var.set_resolution(config[CONF_RESOLUTION])) + cg.add(var.set_request_temperature(CONF_TEMPERATURE in config)) if CONF_DISABLE_LED in config: diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index d80ab1fd1d..0966bd4d97 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -24,7 +24,7 @@ void ArduinoI2CBus::setup() { } next_bus_num++; #elif defined(USE_ESP8266) - wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer) + wire_ = new TwoWire(); // NOLINT(cppcoreguidelines-owning-memory) #elif defined(USE_RP2040) static bool first = true; if (first) { @@ -35,6 +35,16 @@ void ArduinoI2CBus::setup() { } #endif + this->set_pins_and_clock_(); + + this->initialized_ = true; + if (this->scan_) { + ESP_LOGV(TAG, "Scanning i2c bus for active devices..."); + this->i2c_scan_(); + } +} + +void ArduinoI2CBus::set_pins_and_clock_() { #ifdef USE_RP2040 wire_->setSDA(this->sda_pin_); wire_->setSCL(this->scl_pin_); @@ -43,12 +53,8 @@ void ArduinoI2CBus::setup() { wire_->begin(static_cast(sda_pin_), static_cast(scl_pin_)); #endif wire_->setClock(frequency_); - initialized_ = true; - if (this->scan_) { - ESP_LOGV(TAG, "Scanning i2c bus for active devices..."); - this->i2c_scan_(); - } } + void ArduinoI2CBus::dump_config() { ESP_LOGCONFIG(TAG, "I2C Bus:"); ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_); @@ -82,6 +88,10 @@ void ArduinoI2CBus::dump_config() { } ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { +#if defined(USE_ESP8266) + this->set_pins_and_clock_(); // reconfigure Wire global state in case there are multiple instances +#endif + // logging is only enabled with vv level, if warnings are shown the caller // should log them if (!initialized_) { @@ -120,6 +130,10 @@ ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) return ERROR_OK; } ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) { +#if defined(USE_ESP8266) + this->set_pins_and_clock_(); // reconfigure Wire global state in case there are multiple instances +#endif + // logging is only enabled with vv level, if warnings are shown the caller // should log them if (!initialized_) { @@ -164,7 +178,7 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn return ERROR_UNKNOWN; case 2: case 3: - ESP_LOGVV(TAG, "TX failed: not acknowledged"); + ESP_LOGVV(TAG, "TX failed: not acknowledged: %d", status); return ERROR_NOT_ACKNOWLEDGED; case 5: ESP_LOGVV(TAG, "TX failed: timeout"); diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index 7298c3a1c9..6304c2b039 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -30,6 +30,7 @@ class ArduinoI2CBus : public I2CBus, public Component { private: void recover_(); + void set_pins_and_clock_(); RecoveryCode recovery_result_; protected: diff --git a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp index 9e2e3f136a..6e07983920 100644 --- a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp +++ b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp @@ -12,12 +12,12 @@ static const char *const TAG = "audio"; void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { if (call.get_media_url().has_value()) { this->current_url_ = call.get_media_url(); - - if (this->state == media_player::MEDIA_PLAYER_STATE_PLAYING && this->audio_ != nullptr) { + if (this->i2s_state_ != I2S_STATE_STOPPED && this->audio_ != nullptr) { if (this->audio_->isRunning()) { this->audio_->stopSong(); } this->audio_->connecttohost(this->current_url_.value().c_str()); + this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; } else { this->start(); } diff --git a/esphome/components/i2s_audio/microphone/__init__.py b/esphome/components/i2s_audio/microphone/__init__.py index b917da3045..5ee359dc26 100644 --- a/esphome/components/i2s_audio/microphone/__init__.py +++ b/esphome/components/i2s_audio/microphone/__init__.py @@ -20,7 +20,9 @@ DEPENDENCIES = ["i2s_audio"] CONF_ADC_PIN = "adc_pin" CONF_ADC_TYPE = "adc_type" CONF_PDM = "pdm" +CONF_SAMPLE_RATE = "sample_rate" CONF_BITS_PER_SAMPLE = "bits_per_sample" +CONF_USE_APLL = "use_apll" I2SAudioMicrophone = i2s_audio_ns.class_( "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component @@ -62,9 +64,11 @@ BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend( cv.GenerateID(): cv.declare_id(I2SAudioMicrophone), cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), cv.Optional(CONF_CHANNEL, default="right"): cv.enum(CHANNELS), + cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1), cv.Optional(CONF_BITS_PER_SAMPLE, default="32bit"): cv.All( _validate_bits, cv.enum(BITS_PER_SAMPLE) ), + cv.Optional(CONF_USE_APLL, default=False): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -105,6 +109,8 @@ async def to_code(config): cg.add(var.set_pdm(config[CONF_PDM])) cg.add(var.set_channel(config[CONF_CHANNEL])) + cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) + cg.add(var.set_use_apll(config[CONF_USE_APLL])) await microphone.register_microphone(var, config) diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp index ec2fe258c9..602d537bcb 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp @@ -47,14 +47,14 @@ void I2SAudioMicrophone::start_() { } i2s_driver_config_t config = { .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX), - .sample_rate = 16000, + .sample_rate = this->sample_rate_, .bits_per_sample = this->bits_per_sample_, .channel_format = this->channel_, .communication_format = I2S_COMM_FORMAT_STAND_I2S, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 4, .dma_buf_len = 256, - .use_apll = false, + .use_apll = this->use_apll_, .tx_desc_auto_clear = false, .fixed_mclk = 0, .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h index dc6b70047a..68b9a94fbd 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h @@ -31,7 +31,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub #endif void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; } + void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; } void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } + void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; } protected: void start_(); @@ -45,7 +47,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub #endif bool pdm_{false}; i2s_channel_fmt_t channel_; + uint32_t sample_rate_; i2s_bits_per_sample_t bits_per_sample_; + bool use_apll_; HighFrequencyLoopRequester high_freq_; }; diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index cd68f1ae27..3aaf76d6f8 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -17,6 +17,14 @@ from esphome.const import ( CONF_WIDTH, CONF_HEIGHT, CONF_ROTATION, + CONF_MIRROR_X, + CONF_MIRROR_Y, + CONF_SWAP_XY, + CONF_COLOR_ORDER, + CONF_OFFSET_HEIGHT, + CONF_OFFSET_WIDTH, + CONF_TRANSFORM, + CONF_INVERT_COLORS, ) DEPENDENCIES = ["spi"] @@ -43,6 +51,7 @@ ILI9XXXColorMode = ili9xxx_ns.enum("ILI9XXXColorMode") ColorOrder = display.display_ns.enum("ColorMode") MODELS = { + "GC9A01A": ili9xxx_ns.class_("ILI9XXXGC9A01A", ILI9XXXDisplay), "M5STACK": ili9xxx_ns.class_("ILI9XXXM5Stack", ILI9XXXDisplay), "M5CORE": ili9xxx_ns.class_("ILI9XXXM5CORE", ILI9XXXDisplay), "TFT_2.4": ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay), @@ -58,6 +67,7 @@ MODELS = { "ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay), "S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay), "S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay), + "WAVESHARE_RES_3_5": ili9xxx_ns.class_("WAVESHARERES35", ILI9XXXDisplay), } COLOR_ORDERS = { @@ -70,14 +80,6 @@ COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE") CONF_LED_PIN = "led_pin" CONF_COLOR_PALETTE_IMAGES = "color_palette_images" CONF_INVERT_DISPLAY = "invert_display" -CONF_INVERT_COLORS = "invert_colors" -CONF_MIRROR_X = "mirror_x" -CONF_MIRROR_Y = "mirror_y" -CONF_SWAP_XY = "swap_xy" -CONF_COLOR_ORDER = "color_order" -CONF_OFFSET_HEIGHT = "offset_height" -CONF_OFFSET_WIDTH = "offset_width" -CONF_TRANSFORM = "transform" def _validate(config): diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index b315c8be87..e292906a93 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -7,7 +7,6 @@ namespace esphome { namespace ili9xxx { -static const char *const TAG = "ili9xxx"; static const uint16_t SPI_SETUP_US = 100; // estimated fixed overhead in microseconds for an SPI write static const uint16_t SPI_MAX_BLOCK_SIZE = 4092; // Max size of continuous SPI transfer @@ -17,13 +16,7 @@ static inline void put16_be(uint8_t *buf, uint16_t value) { buf[1] = value; } -void ILI9XXXDisplay::setup() { - ESP_LOGD(TAG, "Setting up ILI9xxx"); - - this->setup_pins_(); - this->init_lcd_(); - - this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF); +void ILI9XXXDisplay::set_madctl() { // custom x/y transform and color order uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB; if (this->swap_xy_) @@ -32,13 +25,26 @@ void ILI9XXXDisplay::setup() { mad |= MADCTL_MX; if (this->mirror_y_) mad |= MADCTL_MY; - this->send_command(ILI9XXX_MADCTL, &mad, 1); + this->command(ILI9XXX_MADCTL); + this->data(mad); + esph_log_d(TAG, "Wrote MADCTL 0x%02X", mad); +} +void ILI9XXXDisplay::setup() { + ESP_LOGD(TAG, "Setting up ILI9xxx"); + + this->setup_pins_(); + this->init_lcd_(); + + this->set_madctl(); + this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF); this->x_low_ = this->width_; this->y_low_ = this->height_; this->x_high_ = 0; this->y_high_ = 0; +} +void ILI9XXXDisplay::alloc_buffer_() { if (this->buffer_color_mode_ == BITS_16) { this->init_internal_(this->get_buffer_length_() * 2); if (this->buffer_ != nullptr) { @@ -89,6 +95,7 @@ void ILI9XXXDisplay::dump_config() { LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); + ESP_LOGCONFIG(TAG, " Color order: %s", this->color_order_ == display::COLOR_ORDER_BGR ? "BGR" : "RGB"); ESP_LOGCONFIG(TAG, " Swap_xy: %s", YESNO(this->swap_xy_)); ESP_LOGCONFIG(TAG, " Mirror_x: %s", YESNO(this->mirror_x_)); ESP_LOGCONFIG(TAG, " Mirror_y: %s", YESNO(this->mirror_y_)); @@ -102,6 +109,8 @@ void ILI9XXXDisplay::dump_config() { float ILI9XXXDisplay::get_setup_priority() const { return setup_priority::HARDWARE; } void ILI9XXXDisplay::fill(Color color) { + if (!this->check_buffer_()) + return; uint16_t new_color = 0; this->x_low_ = 0; this->y_low_ = 0; @@ -119,7 +128,6 @@ void ILI9XXXDisplay::fill(Color color) { // Upper and lower is equal can use quicker memset operation. Takes ~20ms. memset(this->buffer_, (uint8_t) new_color, buffer_length_16_bits); } else { - // Slower set of both buffers. Takes ~30ms. for (uint32_t i = 0; i < buffer_length_16_bits; i = i + 2) { this->buffer_[i] = (uint8_t) (new_color >> 8); this->buffer_[i + 1] = (uint8_t) new_color; @@ -139,6 +147,8 @@ void HOT ILI9XXXDisplay::draw_absolute_pixel_internal(int x, int y, Color color) if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) { return; } + if (!this->check_buffer_()) + return; uint32_t pos = (y * width_) + x; uint16_t new_color; bool updated = false; @@ -196,7 +206,6 @@ void ILI9XXXDisplay::display_() { uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE]; // check if something was displayed if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) { - ESP_LOGV(TAG, "Nothing to display"); return; } @@ -211,14 +220,13 @@ void ILI9XXXDisplay::display_() { size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US; ESP_LOGV(TAG, "Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, " - "height:%d, mode=%d, 18bit=%d, sw_time=%dus, mw_time=%dus)", + "height:%zu, mode=%d, 18bit=%d, sw_time=%zuus, mw_time=%zuus)", this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_, this->is_18bitdisplay_, sw_time, mw_time); auto now = millis(); - this->enable(); if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) { // 16 bit mode maps directly to display format - ESP_LOGV(TAG, "Doing single write of %d bytes", this->width_ * h * 2); + ESP_LOGV(TAG, "Doing single write of %zu bytes", this->width_ * h * 2); set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_); this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2); } else { @@ -267,7 +275,7 @@ void ILI9XXXDisplay::display_() { this->write_array(transfer_buffer, idx); } } - this->disable(); + this->end_data_(); ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now)); // invalidate watermarks this->x_low_ = this->width_; @@ -276,6 +284,34 @@ void ILI9XXXDisplay::display_() { this->y_high_ = 0; } +// note that this bypasses the buffer and writes directly to the display. +void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, + display::ColorOrder order, display::ColorBitness bitness, bool big_endian, + int x_offset, int y_offset, int x_pad) { + if (w <= 0 || h <= 0) + return; + // if color mapping or software rotation is required, hand this off to the parent implementation. This will + // do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not + // configured the renderer well. + if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian || + this->is_18bitdisplay_) { + return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, + x_pad); + } + this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1); + // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. + if (x_offset == 0 && x_pad == 0 && y_offset == 0) { + // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother + this->write_array(ptr, w * h * 2); + } else { + auto stride = x_offset + w + x_pad; + for (size_t y = 0; y != h; y++) { + this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2); + } + } + this->end_data_(); +} + // should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color // values per bit is huge uint32_t ILI9XXXDisplay::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); } @@ -299,20 +335,6 @@ void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_byte this->end_data_(); } -uint8_t ILI9XXXDisplay::read_command(uint8_t command_byte, uint8_t index) { - uint8_t data = 0x10 + index; - this->send_command(0xD9, &data, 1); // Set Index Register - uint8_t result; - this->start_command_(); - this->write_byte(command_byte); - this->start_data_(); - do { - result = this->read_byte(); - } while (index--); - this->end_data_(); - return result; -} - void ILI9XXXDisplay::start_command_() { this->dc_pin_->digital_write(false); this->enable(); @@ -328,9 +350,9 @@ void ILI9XXXDisplay::end_data_() { this->disable(); } void ILI9XXXDisplay::reset_() { if (this->reset_pin_ != nullptr) { this->reset_pin_->digital_write(false); - delay(10); + delay(20); this->reset_pin_->digital_write(true); - delay(10); + delay(20); } } @@ -340,7 +362,7 @@ void ILI9XXXDisplay::init_lcd_() { while ((cmd = *addr++) > 0) { x = *addr++; num_args = x & 0x7F; - send_command(cmd, addr, num_args); + this->send_command(cmd, addr, num_args); addr += num_args; if (x & 0x80) delay(150); // NOLINT @@ -348,24 +370,23 @@ void ILI9XXXDisplay::init_lcd_() { } // Tell the display controller where we want to draw pixels. -// when called, the SPI should have already been enabled, only the D/C pin will be toggled here. void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { - uint8_t buf[4]; - this->dc_pin_->digital_write(false); - this->write_byte(ILI9XXX_CASET); // Column address set - put16_be(buf, x1 + this->offset_x_); - put16_be(buf + 2, x2 + this->offset_x_); - this->dc_pin_->digital_write(true); - this->write_array(buf, sizeof buf); - this->dc_pin_->digital_write(false); - this->write_byte(ILI9XXX_PASET); // Row address set - put16_be(buf, y1 + this->offset_y_); - put16_be(buf + 2, y2 + this->offset_y_); - this->dc_pin_->digital_write(true); - this->write_array(buf, sizeof buf); - this->dc_pin_->digital_write(false); - this->write_byte(ILI9XXX_RAMWR); // Write to RAM - this->dc_pin_->digital_write(true); + x1 += this->offset_x_; + x2 += this->offset_x_; + y1 += this->offset_y_; + y2 += this->offset_y_; + this->command(ILI9XXX_CASET); + this->data(x1 >> 8); + this->data(x1 & 0xFF); + this->data(x2 >> 8); + this->data(x2 & 0xFF); + this->command(ILI9XXX_PASET); // Page address set + this->data(y1 >> 8); + this->data(y1 & 0xFF); + this->data(y2 >> 8); + this->data(y2 & 0xFF); + this->command(ILI9XXX_RAMWR); // Write to RAM + this->start_data_(); } void ILI9XXXDisplay::invert_colors(bool invert) { diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index bf4889afe1..11a90e142f 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -1,12 +1,14 @@ #pragma once #include "esphome/components/spi/spi.h" #include "esphome/components/display/display_buffer.h" +#include "esphome/components/display/display_color_utils.h" #include "ili9xxx_defines.h" #include "ili9xxx_init.h" namespace esphome { namespace ili9xxx { +static const char *const TAG = "ili9xxx"; const size_t ILI9XXX_TRANSFER_BUFFER_SIZE = 126; // ensure this is divisible by 6 enum ILI9XXXColorMode { @@ -15,13 +17,9 @@ enum ILI9XXXColorMode { BITS_16 = 0x10, }; -#ifndef ILI9XXXDisplay_DATA_RATE -#define ILI9XXXDisplay_DATA_RATE spi::DATA_RATE_40MHZ -#endif // ILI9XXXDisplay_DATA_RATE - class ILI9XXXDisplay : public display::DisplayBuffer, public spi::SPIDevice { + spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> { public: ILI9XXXDisplay() = default; ILI9XXXDisplay(uint8_t const *init_sequence, int16_t width, int16_t height, bool invert_colors) @@ -31,6 +29,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer, while ((cmd = *addr++) != 0) { num_args = *addr++ & 0x7F; bits = *addr; + esph_log_d(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits); switch (cmd) { case ILI9XXX_MADCTL: { this->swap_xy_ = (bits & MADCTL_MV) != 0; @@ -67,10 +66,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer, this->offset_y_ = offset_y; } void invert_colors(bool invert); - void command(uint8_t value); - void data(uint8_t value); + virtual void command(uint8_t value); + virtual void data(uint8_t value); void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes); - uint8_t read_command(uint8_t command_byte, uint8_t index); void set_color_order(display::ColorOrder color_order) { this->color_order_ = color_order; } void set_swap_xy(bool swap_xy) { this->swap_xy_ = swap_xy; } void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; } @@ -84,11 +82,22 @@ class ILI9XXXDisplay : public display::DisplayBuffer, void setup() override; display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; protected: + inline bool check_buffer_() { + if (this->buffer_ == nullptr) { + this->alloc_buffer_(); + return !this->is_failed(); + } + return true; + } + void draw_absolute_pixel_internal(int x, int y, Color color) override; void setup_pins_(); + virtual void set_madctl(); void display_(); void init_lcd_(); void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2); @@ -115,6 +124,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer, void end_command_(); void start_data_(); void end_data_(); + void alloc_buffer_(); GPIOPin *reset_pin_{nullptr}; GPIOPin *dc_pin_{nullptr}; @@ -124,7 +134,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer, bool need_update_ = false; bool is_18bitdisplay_ = false; bool pre_invertcolors_ = false; - display::ColorOrder color_order_{}; + display::ColorOrder color_order_{display::COLOR_ORDER_BGR}; bool swap_xy_{}; bool mirror_x_{}; bool mirror_y_{}; @@ -178,10 +188,48 @@ class ILI9XXXILI9486 : public ILI9XXXDisplay { ILI9XXXILI9486() : ILI9XXXDisplay(INITCMD_ILI9486, 480, 320, false) {} }; -//----------- ILI9XXX_35_TFT rotated display -------------- class ILI9XXXILI9488 : public ILI9XXXDisplay { public: - ILI9XXXILI9488() : ILI9XXXDisplay(INITCMD_ILI9488, 480, 320, true) {} + ILI9XXXILI9488(const uint8_t *seq = INITCMD_ILI9488) : ILI9XXXDisplay(seq, 480, 320, true) {} + + protected: + void set_madctl() override { + uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB; + uint8_t dfun = 0x22; + this->width_ = 320; + this->height_ = 480; + if (!(this->swap_xy_ || this->mirror_x_ || this->mirror_y_)) { + // no transforms + } else if (this->mirror_y_ && this->mirror_x_) { + // rotate 180 + dfun = 0x42; + } else if (this->swap_xy_) { + this->width_ = 480; + this->height_ = 320; + mad |= 0x20; + if (this->mirror_x_) { + dfun = 0x02; + } else { + dfun = 0x62; + } + } + this->command(ILI9XXX_DFUNCTR); + this->data(0); + this->data(dfun); + this->command(ILI9XXX_MADCTL); + this->data(mad); + } +}; +//----------- Waveshare 3.5 Res Touch - ILI9488 interfaced via 16 bit shift register to parallel */ +class WAVESHARERES35 : public ILI9XXXILI9488 { + public: + WAVESHARERES35() : ILI9XXXILI9488(INITCMD_WAVESHARE_RES_3_5) {} + void data(uint8_t value) override { + this->start_data_(); + this->write_byte(0); + this->write_byte(value); + this->end_data_(); + } }; //----------- ILI9XXX_35_TFT origin colors rotated display -------------- @@ -206,5 +254,10 @@ class ILI9XXXS3BoxLite : public ILI9XXXDisplay { ILI9XXXS3BoxLite() : ILI9XXXDisplay(INITCMD_S3BOXLITE, 320, 240, true) {} }; +class ILI9XXXGC9A01A : public ILI9XXXDisplay { + public: + ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {} +}; + } // namespace ili9xxx } // namespace esphome diff --git a/esphome/components/ili9xxx/ili9xxx_init.h b/esphome/components/ili9xxx/ili9xxx_init.h index a74824052f..ea90f83f30 100644 --- a/esphome/components/ili9xxx/ili9xxx_init.h +++ b/esphome/components/ili9xxx/ili9xxx_init.h @@ -141,7 +141,8 @@ static const uint8_t PROGMEM INITCMD_ILI9486[] = { 0x00 // End of list }; -static const uint8_t PROGMEM INITCMD_ILI9488[] = { + +static const uint8_t INITCMD_ILI9488[] = { ILI9XXX_GMCTRP1,15, 0x0f, 0x24, 0x1c, 0x0a, 0x0f, 0x08, 0x43, 0x88, 0x32, 0x0f, 0x10, 0x06, 0x0f, 0x07, 0x00, ILI9XXX_GMCTRN1,15, 0x0F, 0x38, 0x30, 0x09, 0x0f, 0x0f, 0x4e, 0x77, 0x3c, 0x07, 0x10, 0x05, 0x23, 0x1b, 0x00, @@ -153,28 +154,27 @@ static const uint8_t PROGMEM INITCMD_ILI9488[] = { ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot - ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan - 0xE9, 1, 0x00, // Set Image Functio. Disable 24 bit data ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3 - - ILI9XXX_MADCTL, 1, 0x28, - //ILI9XXX_PIXFMT, 1, 0x55, // Interface Pixel Format = 16bit ILI9XXX_PIXFMT, 1, 0x66, //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode - - - - // 5 frames - //ILI9XXX_ETMOD, 1, 0xC6, // - - ILI9XXX_SLPOUT, 0x80, // Exit sleep mode - //ILI9XXX_INVON , 0, ILI9XXX_DISPON, 0x80, // Set display on 0x00 // end }; +static const uint8_t INITCMD_WAVESHARE_RES_3_5[] = { + ILI9XXX_PWCTR3, 1, 0x33, + ILI9XXX_VMCTR1, 3, 0x00, 0x1e, 0x80, + ILI9XXX_FRMCTR1, 1, 0xA0, + ILI9XXX_GMCTRP1, 15, 0x0, 0x13, 0x18, 0x04, 0x0F, 0x06, 0x3a, 0x56, 0x4d, 0x03, 0x0a, 0x06, 0x30, 0x3e, 0x0f, + ILI9XXX_GMCTRN1, 15, 0x0, 0x13, 0x18, 0x01, 0x11, 0x06, 0x38, 0x34, 0x4d, 0x06, 0x0d, 0x0b, 0x31, 0x37, 0x0f, + ILI9XXX_PIXFMT, 1, 0x55, + ILI9XXX_SLPOUT, 0x80, // slpout, delay + ILI9XXX_DISPON, 0, + 0x00 // End of list +}; + static const uint8_t PROGMEM INITCMD_ILI9488_A[] = { ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F, ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F, @@ -316,6 +316,60 @@ static const uint8_t PROGMEM INITCMD_ST7789V[] = { 0x00 // End of list }; +static const uint8_t PROGMEM INITCMD_GC9A01A[] = { + 0xEF, 0, + 0xEB, 1, 0x14, // ? + 0xFE, 0, + 0xEF, 0, + 0xEB, 1, 0x14, // ? + 0x84, 1, 0x40, // ? + 0x85, 1, 0xFF, // ? + 0x86, 1, 0xFF, // ? + 0x87, 1, 0xFF, // ? + 0x88, 1, 0x0A, // ? + 0x89, 1, 0x21, // ? + 0x8A, 1, 0x00, // ? + 0x8B, 1, 0x80, // ? + 0x8C, 1, 0x01, // ? + 0x8D, 1, 0x01, // ? + 0x8E, 1, 0xFF, // ? + 0x8F, 1, 0xFF, // ? + 0xB6, 2, 0x00, 0x00, // ? + 0x90, 4, 0x08, 0x08, 0x08, 0x08, // ? + ILI9XXX_PIXFMT , 1, 0x05, + ILI9XXX_MADCTL , 1, MADCTL_MX| MADCTL_BGR, // Memory Access Control + 0xBD, 1, 0x06, // ? + 0xBC, 1, 0x00, // ? + 0xFF, 3, 0x60, 0x01, 0x04, // ? + 0xC3, 1, 0x13, + 0xC4, 1, 0x13, + 0xF9, 1, 0x22, + 0xBE, 1, 0x11, // ? + 0xE1, 2, 0x10, 0x0E, // ? + 0xDF, 3, 0x21, 0x0c, 0x02, // ? + 0xF0, 6, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A, + 0xF1, 6, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F, + 0xF2, 6, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A, + 0xF3, 6, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F, + 0xED, 2, 0x1B, 0x0B, // ? + 0xAE, 1, 0x77, // ? + 0xCD, 1, 0x63, // ? + 0xE8, 1, 0x34, + 0x62, 12, 0x18, 0x0D, 0x71, 0xED, 0x70, 0x70, // ? + 0x18, 0x0F, 0x71, 0xEF, 0x70, 0x70, + 0x63, 12, 0x18, 0x11, 0x71, 0xF1, 0x70, 0x70, // ? + 0x18, 0x13, 0x71, 0xF3, 0x70, 0x70, + 0x64, 7, 0x28, 0x29, 0xF1, 0x01, 0xF1, 0x00, 0x07, // ? + 0x66, 10, 0x3C, 0x00, 0xCD, 0x67, 0x45, 0x45, 0x10, 0x00, 0x00, 0x00, // ? + 0x67, 10, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x54, 0x10, 0x32, 0x98, // ? + 0x74, 7, 0x10, 0x85, 0x80, 0x00, 0x00, 0x4E, 0x00, // ? + 0x98, 2, 0x3e, 0x07, // ? + 0x35, 0, + ILI9XXX_SLPOUT , 0x80, // Exit Sleep + ILI9XXX_DISPON , 0x80, // Display on + 0x00 // End of list +}; + // clang-format on } // namespace ili9xxx } // namespace esphome diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index c11021fc9c..73dc73aa45 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -36,6 +36,7 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "image" DEPENDENCIES = ["display"] MULTI_CONF = True +MULTI_CONF_NO_DEFAULT = True image_ns = cg.esphome_ns.namespace("image") diff --git a/esphome/components/image/image.h b/esphome/components/image/image.h index 4e869f5204..5f1f50a134 100644 --- a/esphome/components/image/image.h +++ b/esphome/components/image/image.h @@ -37,6 +37,7 @@ class Image : public display::BaseImage { Color get_pixel(int x, int y, Color color_on = display::COLOR_ON, Color color_off = display::COLOR_OFF) const; int get_width() const override; int get_height() const override; + const uint8_t *get_data_start() { return this->data_start_; } ImageType get_type() const; void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override; diff --git a/esphome/components/improv_base/improv_base.cpp b/esphome/components/improv_base/improv_base.cpp index f18a1061fb..e890187d1a 100644 --- a/esphome/components/improv_base/improv_base.cpp +++ b/esphome/components/improv_base/improv_base.cpp @@ -21,8 +21,13 @@ std::string ImprovBase::get_formatted_next_url_() { // Ip address pos = this->next_url_.find("{{ip_address}}"); if (pos != std::string::npos) { - std::string ip = network::get_ip_address().str(); - copy.replace(pos, 14, ip); + for (auto &ip : network::get_ip_addresses()) { + if (ip.is_ip4()) { + std::string ipa = ip.str(); + copy.replace(pos, 14, ipa); + break; + } + } } return copy; diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index 600069b781..40297bee68 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -52,7 +52,7 @@ optional ImprovSerialComponent::read_byte_() { size_t available; uart_get_buffered_data_len(this->uart_num_, &available); if (available) { - uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_PERIOD_MS); + uart_read_bytes(this->uart_num_, &data, 1, 0); byte = data; } } @@ -71,7 +71,7 @@ optional ImprovSerialComponent::read_byte_() { #endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) case logger::UART_SELECTION_USB_SERIAL_JTAG: { - if (usb_serial_jtag_read_bytes((char *) &data, 1, 20 / portTICK_PERIOD_MS)) { + if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) { byte = data; } break; @@ -155,9 +155,13 @@ std::vector ImprovSerialComponent::build_rpc_settings_response_(improv: urls.push_back(this->get_formatted_next_url_()); } #ifdef USE_WEBSERVER - auto ip = wifi::global_wifi_component->wifi_sta_ip(); - std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); - urls.push_back(webserver_url); + for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) { + if (ip.is_ip4()) { + std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); + urls.push_back(webserver_url); + break; + } + } #endif std::vector data = improv::build_rpc_response(command, urls, false); return data; @@ -192,7 +196,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command this->connecting_sta_ = sta; wifi::global_wifi_component->set_sta(sta); - wifi::global_wifi_component->start_scanning(); + wifi::global_wifi_component->start_connecting(sta, false); this->set_state_(improv::STATE_PROVISIONING); ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), command.password.c_str()); diff --git a/esphome/components/ina226/__init__.py b/esphome/components/ina226/__init__.py index e69de29bb2..ca1f2caa31 100644 --- a/esphome/components/ina226/__init__.py +++ b/esphome/components/ina226/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@Sergio303", "@latonita"] diff --git a/esphome/components/ina226/ina226.cpp b/esphome/components/ina226/ina226.cpp index 1fb859da66..7a57c118af 100644 --- a/esphome/components/ina226/ina226.cpp +++ b/esphome/components/ina226/ina226.cpp @@ -33,31 +33,37 @@ static const uint8_t INA226_REGISTER_POWER = 0x03; static const uint8_t INA226_REGISTER_CURRENT = 0x04; static const uint8_t INA226_REGISTER_CALIBRATION = 0x05; +static const uint16_t INA226_ADC_TIMES[] = {140, 204, 332, 588, 1100, 2116, 4156, 8244}; +static const uint16_t INA226_ADC_AVG_SAMPLES[] = {1, 4, 16, 64, 128, 256, 512, 1024}; + void INA226Component::setup() { ESP_LOGCONFIG(TAG, "Setting up INA226..."); - // Config Register - // 0bx000000000000000 << 15 RESET Bit (1 -> trigger reset) - if (!this->write_byte_16(INA226_REGISTER_CONFIG, 0x8000)) { + + ConfigurationRegister config; + + config.reset = 1; + if (!this->write_byte_16(INA226_REGISTER_CONFIG, config.raw)) { this->mark_failed(); return; } delay(1); - uint16_t config = 0x0000; + config.raw = 0; + config.reserved = 0b100; // as per datasheet // Averaging Mode AVG Bit Settings[11:9] (000 -> 1 sample, 001 -> 4 sample, 111 -> 1024 samples) - config |= 0b0000001000000000; + config.avg_samples = this->adc_avg_samples_; // Bus Voltage Conversion Time VBUSCT Bit Settings [8:6] (100 -> 1.1ms, 111 -> 8.244 ms) - config |= 0b0000000100000000; + config.bus_voltage_conversion_time = this->adc_time_voltage_; // Shunt Voltage Conversion Time VSHCT Bit Settings [5:3] (100 -> 1.1ms, 111 -> 8.244 ms) - config |= 0b0000000000100000; + config.shunt_voltage_conversion_time = this->adc_time_current_; // Mode Settings [2:0] Combinations (111 -> Shunt and Bus, Continuous) - config |= 0b0000000000000111; + config.mode = 0b111; - if (!this->write_byte_16(INA226_REGISTER_CONFIG, config)) { + if (!this->write_byte_16(INA226_REGISTER_CONFIG, config.raw)) { this->mark_failed(); return; } @@ -87,6 +93,10 @@ void INA226Component::dump_config() { } LOG_UPDATE_INTERVAL(this); + ESP_LOGCONFIG(TAG, " ADC Conversion Time Bus Voltage: %d", INA226_ADC_TIMES[this->adc_time_voltage_ & 0b111]); + ESP_LOGCONFIG(TAG, " ADC Conversion Time Shunt Voltage: %d", INA226_ADC_TIMES[this->adc_time_current_ & 0b111]); + ESP_LOGCONFIG(TAG, " ADC Averaging Samples: %d", INA226_ADC_AVG_SAMPLES[this->adc_avg_samples_ & 0b111]); + LOG_SENSOR(" ", "Bus Voltage", this->bus_voltage_sensor_); LOG_SENSOR(" ", "Shunt Voltage", this->shunt_voltage_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_); @@ -102,7 +112,9 @@ void INA226Component::update() { this->status_set_warning(); return; } - float bus_voltage_v = int16_t(raw_bus_voltage) * 0.00125f; + // Convert for 2's compliment and signed value (though always positive) + float bus_voltage_v = this->twos_complement_(raw_bus_voltage, 16); + bus_voltage_v *= 0.00125f; this->bus_voltage_sensor_->publish_state(bus_voltage_v); } @@ -112,7 +124,9 @@ void INA226Component::update() { this->status_set_warning(); return; } - float shunt_voltage_v = int16_t(raw_shunt_voltage) * 0.0000025f; + // Convert for 2's compliment and signed value + float shunt_voltage_v = this->twos_complement_(raw_shunt_voltage, 16); + shunt_voltage_v *= 0.0000025f; this->shunt_voltage_sensor_->publish_state(shunt_voltage_v); } @@ -122,7 +136,9 @@ void INA226Component::update() { this->status_set_warning(); return; } - float current_ma = int16_t(raw_current) * (this->calibration_lsb_ / 1000.0f); + // Convert for 2's compliment and signed value + float current_ma = this->twos_complement_(raw_current, 16); + current_ma *= (this->calibration_lsb_ / 1000.0f); this->current_sensor_->publish_state(current_ma / 1000.0f); } @@ -139,5 +155,12 @@ void INA226Component::update() { this->status_clear_warning(); } +int32_t INA226Component::twos_complement_(int32_t val, uint8_t bits) { + if (val & ((uint32_t) 1 << (bits - 1))) { + val -= (uint32_t) 1 << bits; + } + return val; +} + } // namespace ina226 } // namespace esphome diff --git a/esphome/components/ina226/ina226.h b/esphome/components/ina226/ina226.h index a551cb3430..61214fea0e 100644 --- a/esphome/components/ina226/ina226.h +++ b/esphome/components/ina226/ina226.h @@ -7,6 +7,40 @@ namespace esphome { namespace ina226 { +enum AdcTime : uint16_t { + ADC_TIME_140US = 0, + ADC_TIME_204US = 1, + ADC_TIME_332US = 2, + ADC_TIME_588US = 3, + ADC_TIME_1100US = 4, + ADC_TIME_2116US = 5, + ADC_TIME_4156US = 6, + ADC_TIME_8244US = 7 +}; + +enum AdcAvgSamples : uint16_t { + ADC_AVG_SAMPLES_1 = 0, + ADC_AVG_SAMPLES_4 = 1, + ADC_AVG_SAMPLES_16 = 2, + ADC_AVG_SAMPLES_64 = 3, + ADC_AVG_SAMPLES_128 = 4, + ADC_AVG_SAMPLES_256 = 5, + ADC_AVG_SAMPLES_512 = 6, + ADC_AVG_SAMPLES_1024 = 7 +}; + +union ConfigurationRegister { + uint16_t raw; + struct { + uint16_t mode : 3; + AdcTime shunt_voltage_conversion_time : 3; + AdcTime bus_voltage_conversion_time : 3; + AdcAvgSamples avg_samples : 3; + uint16_t reserved : 3; + uint16_t reset : 1; + } __attribute__((packed)); +}; + class INA226Component : public PollingComponent, public i2c::I2CDevice { public: void setup() override; @@ -16,6 +50,10 @@ class INA226Component : public PollingComponent, public i2c::I2CDevice { void set_shunt_resistance_ohm(float shunt_resistance_ohm) { shunt_resistance_ohm_ = shunt_resistance_ohm; } void set_max_current_a(float max_current_a) { max_current_a_ = max_current_a; } + void set_adc_time_voltage(AdcTime time) { adc_time_voltage_ = time; } + void set_adc_time_current(AdcTime time) { adc_time_current_ = time; } + void set_adc_avg_samples(AdcAvgSamples samples) { adc_avg_samples_ = samples; } + void set_bus_voltage_sensor(sensor::Sensor *bus_voltage_sensor) { bus_voltage_sensor_ = bus_voltage_sensor; } void set_shunt_voltage_sensor(sensor::Sensor *shunt_voltage_sensor) { shunt_voltage_sensor_ = shunt_voltage_sensor; } void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } @@ -24,11 +62,16 @@ class INA226Component : public PollingComponent, public i2c::I2CDevice { protected: float shunt_resistance_ohm_; float max_current_a_; + AdcTime adc_time_voltage_{AdcTime::ADC_TIME_1100US}; + AdcTime adc_time_current_{AdcTime::ADC_TIME_1100US}; + AdcAvgSamples adc_avg_samples_{AdcAvgSamples::ADC_AVG_SAMPLES_4}; uint32_t calibration_lsb_; sensor::Sensor *bus_voltage_sensor_{nullptr}; sensor::Sensor *shunt_voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr}; + + int32_t twos_complement_(int32_t val, uint8_t bits); }; } // namespace ina226 diff --git a/esphome/components/ina226/sensor.py b/esphome/components/ina226/sensor.py index ee4036ce7e..0accc58c00 100644 --- a/esphome/components/ina226/sensor.py +++ b/esphome/components/ina226/sensor.py @@ -16,15 +16,49 @@ from esphome.const import ( UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, + CONF_VOLTAGE, ) DEPENDENCIES = ["i2c"] +CONF_ADC_AVERAGING = "adc_averaging" +CONF_ADC_TIME = "adc_time" + ina226_ns = cg.esphome_ns.namespace("ina226") INA226Component = ina226_ns.class_( "INA226Component", cg.PollingComponent, i2c.I2CDevice ) +AdcTime = ina226_ns.enum("AdcTime") +ADC_TIMES = { + 140: AdcTime.ADC_TIME_140US, + 204: AdcTime.ADC_TIME_204US, + 332: AdcTime.ADC_TIME_332US, + 588: AdcTime.ADC_TIME_588US, + 1100: AdcTime.ADC_TIME_1100US, + 2116: AdcTime.ADC_TIME_2116US, + 4156: AdcTime.ADC_TIME_4156US, + 8244: AdcTime.ADC_TIME_8244US, +} + +AdcAvgSamples = ina226_ns.enum("AdcAvgSamples") +ADC_AVG_SAMPLES = { + 1: AdcAvgSamples.ADC_AVG_SAMPLES_1, + 4: AdcAvgSamples.ADC_AVG_SAMPLES_4, + 16: AdcAvgSamples.ADC_AVG_SAMPLES_16, + 64: AdcAvgSamples.ADC_AVG_SAMPLES_64, + 128: AdcAvgSamples.ADC_AVG_SAMPLES_128, + 256: AdcAvgSamples.ADC_AVG_SAMPLES_256, + 512: AdcAvgSamples.ADC_AVG_SAMPLES_512, + 1024: AdcAvgSamples.ADC_AVG_SAMPLES_1024, +} + + +def validate_adc_time(value): + value = cv.positive_time_period_microseconds(value).total_microseconds + return cv.enum(ADC_TIMES, int=True)(value) + + CONFIG_SCHEMA = ( cv.Schema( { @@ -59,6 +93,18 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_MAX_CURRENT, default=3.2): cv.All( cv.current, cv.Range(min=0.0) ), + cv.Optional(CONF_ADC_TIME, default="1100 us"): cv.Any( + validate_adc_time, + cv.Schema( + { + cv.Required(CONF_VOLTAGE): validate_adc_time, + cv.Required(CONF_CURRENT): validate_adc_time, + } + ), + ), + cv.Optional(CONF_ADC_AVERAGING, default=4): cv.enum( + ADC_AVG_SAMPLES, int=True + ), } ) .extend(cv.polling_component_schema("60s")) @@ -72,9 +118,18 @@ async def to_code(config): await i2c.register_i2c_device(var, config) cg.add(var.set_shunt_resistance_ohm(config[CONF_SHUNT_RESISTANCE])) - cg.add(var.set_max_current_a(config[CONF_MAX_CURRENT])) + adc_time_config = config[CONF_ADC_TIME] + if isinstance(adc_time_config, dict): + cg.add(var.set_adc_time_voltage(adc_time_config[CONF_VOLTAGE])) + cg.add(var.set_adc_time_current(adc_time_config[CONF_CURRENT])) + else: + cg.add(var.set_adc_time_voltage(adc_time_config)) + cg.add(var.set_adc_time_current(adc_time_config)) + + cg.add(var.set_adc_avg_samples(config[CONF_ADC_AVERAGING])) + if CONF_BUS_VOLTAGE in config: sens = await sensor.new_sensor(config[CONF_BUS_VOLTAGE]) cg.add(var.set_bus_voltage_sensor(sens)) diff --git a/esphome/components/inkbird_ibsth1_mini/sensor.py b/esphome/components/inkbird_ibsth1_mini/sensor.py index aa11fb3172..cdd0b5ade5 100644 --- a/esphome/components/inkbird_ibsth1_mini/sensor.py +++ b/esphome/components/inkbird_ibsth1_mini/sensor.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker from esphome.const import ( CONF_BATTERY_LEVEL, + CONF_EXTERNAL_TEMPERATURE, CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, @@ -19,8 +20,6 @@ from esphome.const import ( CODEOWNERS = ["@fkirill"] DEPENDENCIES = ["esp32_ble_tracker"] -CONF_EXTERNAL_TEMPERATURE = "external_temperature" - inkbird_ibsth1_mini_ns = cg.esphome_ns.namespace("inkbird_ibsth1_mini") InkbirdIbstH1Mini = inkbird_ibsth1_mini_ns.class_( "InkbirdIbstH1Mini", esp32_ble_tracker.ESPBTDeviceListener, cg.Component diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp index 92a226de87..f4d0fedf83 100644 --- a/esphome/components/inkplate6/inkplate.cpp +++ b/esphome/components/inkplate6/inkplate.cpp @@ -55,6 +55,9 @@ void Inkplate6::setup() { this->wakeup_pin_->digital_write(false); } +/** + * Allocate buffers. May be called after setup to re-initialise if e.g. greyscale is changed. + */ void Inkplate6::initialize_() { ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); ExternalRAMAllocator allocator32(ExternalRAMAllocator::ALLOW_FAILURE); diff --git a/esphome/components/inkplate6/inkplate.h b/esphome/components/inkplate6/inkplate.h index 307d9671e6..2946c89e1c 100644 --- a/esphome/components/inkplate6/inkplate.h +++ b/esphome/components/inkplate6/inkplate.h @@ -68,8 +68,9 @@ class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice { void set_greyscale(bool greyscale) { this->greyscale_ = greyscale; - this->initialize_(); this->block_partial_ = true; + if (this->is_ready()) + this->initialize_(); } void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; } void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } diff --git a/esphome/components/jsn_sr04t/__init__.py b/esphome/components/jsn_sr04t/__init__.py new file mode 100644 index 0000000000..ef6335f316 --- /dev/null +++ b/esphome/components/jsn_sr04t/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@Mafus1"] diff --git a/esphome/components/jsn_sr04t/jsn_sr04t.cpp b/esphome/components/jsn_sr04t/jsn_sr04t.cpp new file mode 100644 index 0000000000..70e21a137d --- /dev/null +++ b/esphome/components/jsn_sr04t/jsn_sr04t.cpp @@ -0,0 +1,58 @@ +#include "jsn_sr04t.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include + +// Very basic support for JSN_SR04T V3.0 distance sensor in mode 2 + +namespace esphome { +namespace jsn_sr04t { + +static const char *const TAG = "jsn_sr04t.sensor"; + +void Jsnsr04tComponent::update() { + this->write_byte(0x55); + ESP_LOGV(TAG, "Request read out from sensor"); +} + +void Jsnsr04tComponent::loop() { + while (this->available() > 0) { + uint8_t data; + this->read_byte(&data); + + ESP_LOGV(TAG, "Read byte from sensor: %x", data); + + if (this->buffer_.empty() && data != 0xFF) + continue; + + this->buffer_.push_back(data); + if (this->buffer_.size() == 4) + this->check_buffer_(); + } +} + +void Jsnsr04tComponent::check_buffer_() { + uint8_t checksum = this->buffer_[0] + this->buffer_[1] + this->buffer_[2]; + if (this->buffer_[3] == checksum) { + uint16_t distance = encode_uint16(this->buffer_[1], this->buffer_[2]); + if (distance > 250) { + float meters = distance / 1000.0f; + ESP_LOGV(TAG, "Distance from sensor: %" PRIu32 "mm, %.3fm", distance, meters); + this->publish_state(meters); + } else { + ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str()); + } + } else { + ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]); + } + this->buffer_.clear(); +} + +void Jsnsr04tComponent::dump_config() { + LOG_SENSOR("", "JST_SR04T Sensor", this); + LOG_UPDATE_INTERVAL(this); +} + +} // namespace jsn_sr04t +} // namespace esphome diff --git a/esphome/components/jsn_sr04t/jsn_sr04t.h b/esphome/components/jsn_sr04t/jsn_sr04t.h new file mode 100644 index 0000000000..bd43252be8 --- /dev/null +++ b/esphome/components/jsn_sr04t/jsn_sr04t.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace jsn_sr04t { + +class Jsnsr04tComponent : public sensor::Sensor, public PollingComponent, public uart::UARTDevice { + public: + // Nothing really public. + + // ========== INTERNAL METHODS ========== + void update() override; + void loop() override; + void dump_config() override; + + protected: + void check_buffer_(); + + std::vector buffer_; +}; + +} // namespace jsn_sr04t +} // namespace esphome diff --git a/esphome/components/jsn_sr04t/sensor.py b/esphome/components/jsn_sr04t/sensor.py new file mode 100644 index 0000000000..4b062e81e9 --- /dev/null +++ b/esphome/components/jsn_sr04t/sensor.py @@ -0,0 +1,44 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, uart +from esphome.const import ( + STATE_CLASS_MEASUREMENT, + UNIT_METER, + ICON_ARROW_EXPAND_VERTICAL, +) + +CODEOWNERS = ["@Mafus1"] +DEPENDENCIES = ["uart"] + +jsn_sr04t_ns = cg.esphome_ns.namespace("jsn_sr04t") +Jsnsr04tComponent = jsn_sr04t_ns.class_( + "Jsnsr04tComponent", sensor.Sensor, cg.PollingComponent, uart.UARTDevice +) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + Jsnsr04tComponent, + unit_of_measurement=UNIT_METER, + icon=ICON_ARROW_EXPAND_VERTICAL, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "jsn_sr04t", + baud_rate=9600, + require_tx=True, + require_rx=True, + data_bits=8, + parity=None, + stop_bits=1, +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) diff --git a/esphome/components/kalman_combinator/__init__.py b/esphome/components/kalman_combinator/__init__.py index 3356e61bb2..e69de29bb2 100644 --- a/esphome/components/kalman_combinator/__init__.py +++ b/esphome/components/kalman_combinator/__init__.py @@ -1 +0,0 @@ -CODEOWNERS = ["@Cat-Ion"] diff --git a/esphome/components/kalman_combinator/kalman_combinator.cpp b/esphome/components/kalman_combinator/kalman_combinator.cpp deleted file mode 100644 index 50d8f03a93..0000000000 --- a/esphome/components/kalman_combinator/kalman_combinator.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "kalman_combinator.h" -#include "esphome/core/hal.h" -#include -#include - -namespace esphome { -namespace kalman_combinator { - -void KalmanCombinatorComponent::dump_config() { - ESP_LOGCONFIG("kalman_combinator", "Kalman Combinator:"); - ESP_LOGCONFIG("kalman_combinator", " Update variance: %f per ms", this->update_variance_value_); - ESP_LOGCONFIG("kalman_combinator", " Sensors:"); - for (const auto &sensor : this->sensors_) { - auto &entity = *sensor.first; - ESP_LOGCONFIG("kalman_combinator", " - %s", entity.get_name().c_str()); - } -} - -void KalmanCombinatorComponent::setup() { - for (const auto &sensor : this->sensors_) { - const auto stddev = sensor.second; - sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); }); - } -} - -void KalmanCombinatorComponent::add_source(Sensor *sensor, std::function const &stddev) { - this->sensors_.emplace_back(sensor, stddev); -} - -void KalmanCombinatorComponent::add_source(Sensor *sensor, float stddev) { - this->add_source(sensor, std::function{[stddev](float x) -> float { return stddev; }}); -} - -void KalmanCombinatorComponent::update_variance_() { - uint32_t now = millis(); - - // Variance increases by update_variance_ each millisecond - auto dt = now - this->last_update_; - auto dv = this->update_variance_value_ * dt; - this->variance_ += dv; - this->last_update_ = now; -} - -void KalmanCombinatorComponent::correct_(float value, float stddev) { - if (std::isnan(value) || std::isinf(stddev)) { - return; - } - - if (std::isnan(this->state_) || std::isinf(this->variance_)) { - this->state_ = value; - this->variance_ = stddev * stddev; - if (this->std_dev_sensor_ != nullptr) { - this->std_dev_sensor_->publish_state(stddev); - } - return; - } - - this->update_variance_(); - - // Combine two gaussian distributions mu1+-var1, mu2+-var2 to a new one around mu - // Use the value with the smaller variance as mu1 to prevent precision errors - const bool this_first = this->variance_ < (stddev * stddev); - const float mu1 = this_first ? this->state_ : value; - const float mu2 = this_first ? value : this->state_; - - const float var1 = this_first ? this->variance_ : stddev * stddev; - const float var2 = this_first ? stddev * stddev : this->variance_; - - const float mu = mu1 + var1 * (mu2 - mu1) / (var1 + var2); - const float var = var1 - (var1 * var1) / (var1 + var2); - - // Update and publish state - this->state_ = mu; - this->variance_ = var; - - this->publish_state(mu); - if (this->std_dev_sensor_ != nullptr) { - this->std_dev_sensor_->publish_state(std::sqrt(var)); - } -} -} // namespace kalman_combinator -} // namespace esphome diff --git a/esphome/components/kalman_combinator/kalman_combinator.h b/esphome/components/kalman_combinator/kalman_combinator.h deleted file mode 100644 index afbe3ece92..0000000000 --- a/esphome/components/kalman_combinator/kalman_combinator.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include -#include - -namespace esphome { -namespace kalman_combinator { - -class KalmanCombinatorComponent : public Component, public sensor::Sensor { - public: - KalmanCombinatorComponent() = default; - - float get_setup_priority() const override { return esphome::setup_priority::DATA; } - - void dump_config() override; - void setup() override; - - void add_source(Sensor *sensor, std::function const &stddev); - void add_source(Sensor *sensor, float stddev); - void set_process_std_dev(float process_std_dev) { - this->update_variance_value_ = process_std_dev * process_std_dev * 0.001f; - } - void set_std_dev_sensor(Sensor *sensor) { this->std_dev_sensor_ = sensor; } - - private: - void update_variance_(); - void correct_(float value, float stddev); - - // Source sensors and their error functions - std::vector>> sensors_; - - // Optional sensor for publishing the current error - sensor::Sensor *std_dev_sensor_{nullptr}; - - // Tick of the last update - uint32_t last_update_{0}; - // Change of the variance, per ms - float update_variance_value_{0.f}; - - // Best guess for the state and its variance - float state_{NAN}; - float variance_{INFINITY}; -}; -} // namespace kalman_combinator -} // namespace esphome diff --git a/esphome/components/kalman_combinator/sensor.py b/esphome/components/kalman_combinator/sensor.py index 28b96077cc..eca1ba7b85 100644 --- a/esphome/components/kalman_combinator/sensor.py +++ b/esphome/components/kalman_combinator/sensor.py @@ -1,90 +1,6 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import sensor -from esphome.const import ( - CONF_ID, - CONF_SOURCE, - CONF_ACCURACY_DECIMALS, - CONF_DEVICE_CLASS, - CONF_ENTITY_CATEGORY, - CONF_ICON, - CONF_UNIT_OF_MEASUREMENT, + +CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( + "The kalman_combinator sensor has moved.\nPlease use the combination platform instead with type: kalman.\n" + "See https://esphome.io/components/sensor/combination.html" ) -from esphome.core.entity_helpers import inherit_property_from - -kalman_combinator_ns = cg.esphome_ns.namespace("kalman_combinator") -KalmanCombinatorComponent = kalman_combinator_ns.class_( - "KalmanCombinatorComponent", cg.Component, sensor.Sensor -) - -CONF_ERROR = "error" -CONF_SOURCES = "sources" -CONF_PROCESS_STD_DEV = "process_std_dev" -CONF_STD_DEV = "std_dev" - - -CONFIG_SCHEMA = ( - sensor.sensor_schema(KalmanCombinatorComponent) - .extend(cv.COMPONENT_SCHEMA) - .extend( - { - cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float, - cv.Required(CONF_SOURCES): cv.ensure_list( - cv.Schema( - { - cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor), - cv.Required(CONF_ERROR): cv.templatable(cv.positive_float), - } - ), - ), - cv.Optional(CONF_STD_DEV): sensor.sensor_schema(), - } - ) -) - -# Inherit some sensor values from the first source, for both the state and the error value -properties_to_inherit = [ - CONF_ACCURACY_DECIMALS, - CONF_DEVICE_CLASS, - CONF_ENTITY_CATEGORY, - CONF_ICON, - CONF_UNIT_OF_MEASUREMENT, - # CONF_STATE_CLASS could also be inherited, but might lead to unexpected behaviour with "total_increasing" -] -inherit_schema_for_state = [ - inherit_property_from(property, [CONF_SOURCES, 0, CONF_SOURCE]) - for property in properties_to_inherit -] -inherit_schema_for_std_dev = [ - inherit_property_from([CONF_STD_DEV, property], [CONF_SOURCES, 0, CONF_SOURCE]) - for property in properties_to_inherit -] - -FINAL_VALIDATE_SCHEMA = cv.All( - CONFIG_SCHEMA.extend( - {cv.Required(CONF_ID): cv.use_id(KalmanCombinatorComponent)}, - extra=cv.ALLOW_EXTRA, - ), - *inherit_schema_for_state, - *inherit_schema_for_std_dev, -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await sensor.register_sensor(var, config) - - cg.add(var.set_process_std_dev(config[CONF_PROCESS_STD_DEV])) - for source_conf in config[CONF_SOURCES]: - source = await cg.get_variable(source_conf[CONF_SOURCE]) - error = await cg.templatable( - source_conf[CONF_ERROR], - [(float, "x")], - cg.float_, - ) - cg.add(var.add_source(source, error)) - - if CONF_STD_DEV in config: - sens = await sensor.new_sensor(config[CONF_STD_DEV]) - cg.add(var.set_std_dev_sensor(sens)) diff --git a/esphome/components/kamstrup_kmp/__init__.py b/esphome/components/kamstrup_kmp/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp b/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp new file mode 100644 index 0000000000..b870d1b56d --- /dev/null +++ b/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp @@ -0,0 +1,301 @@ +#include "kamstrup_kmp.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace kamstrup_kmp { + +static const char *const TAG = "kamstrup_kmp"; + +void KamstrupKMPComponent::dump_config() { + ESP_LOGCONFIG(TAG, "kamstrup_kmp:"); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with Kamstrup meter failed!"); + } + LOG_UPDATE_INTERVAL(this); + + LOG_SENSOR(" ", "Heat Energy", this->heat_energy_sensor_); + LOG_SENSOR(" ", "Power", this->power_sensor_); + LOG_SENSOR(" ", "Temperature 1", this->temp1_sensor_); + LOG_SENSOR(" ", "Temperature 2", this->temp2_sensor_); + LOG_SENSOR(" ", "Temperature Difference", this->temp_diff_sensor_); + LOG_SENSOR(" ", "Flow", this->flow_sensor_); + LOG_SENSOR(" ", "Volume", this->volume_sensor_); + + for (int i = 0; i < this->custom_sensors_.size(); i++) { + LOG_SENSOR(" ", "Custom Sensor", this->custom_sensors_[i]); + ESP_LOGCONFIG(TAG, " Command: 0x%04X", this->custom_commands_[i]); + } + + this->check_uart_settings(1200, 2, uart::UART_CONFIG_PARITY_NONE, 8); +} + +float KamstrupKMPComponent::get_setup_priority() const { return setup_priority::DATA; } + +void KamstrupKMPComponent::update() { + if (this->heat_energy_sensor_ != nullptr) { + this->command_queue_.push(CMD_HEAT_ENERGY); + } + + if (this->power_sensor_ != nullptr) { + this->command_queue_.push(CMD_POWER); + } + + if (this->temp1_sensor_ != nullptr) { + this->command_queue_.push(CMD_TEMP1); + } + + if (this->temp2_sensor_ != nullptr) { + this->command_queue_.push(CMD_TEMP2); + } + + if (this->temp_diff_sensor_ != nullptr) { + this->command_queue_.push(CMD_TEMP_DIFF); + } + + if (this->flow_sensor_ != nullptr) { + this->command_queue_.push(CMD_FLOW); + } + + if (this->volume_sensor_ != nullptr) { + this->command_queue_.push(CMD_VOLUME); + } + + for (uint16_t custom_command : this->custom_commands_) { + this->command_queue_.push(custom_command); + } +} + +void KamstrupKMPComponent::loop() { + if (!this->command_queue_.empty()) { + uint16_t command = this->command_queue_.front(); + this->send_command_(command); + this->command_queue_.pop(); + } +} + +void KamstrupKMPComponent::send_command_(uint16_t command) { + uint32_t msg_len = 5; + uint8_t msg[msg_len]; + + msg[0] = 0x3F; + msg[1] = 0x10; + msg[2] = 0x01; + msg[3] = command >> 8; + msg[4] = command & 0xFF; + + this->clear_uart_rx_buffer_(); + this->send_message_(msg, msg_len); + this->read_command_(command); +} + +void KamstrupKMPComponent::send_message_(const uint8_t *msg, int msg_len) { + int buffer_len = msg_len + 2; + uint8_t buffer[buffer_len]; + + // Prepare the basic message and appand CRC + for (int i = 0; i < msg_len; i++) { + buffer[i] = msg[i]; + } + + buffer[buffer_len - 2] = 0; + buffer[buffer_len - 1] = 0; + + uint16_t crc = crc16_ccitt(buffer, buffer_len); + buffer[buffer_len - 2] = crc >> 8; + buffer[buffer_len - 1] = crc & 0xFF; + + // Prepare actual TX message + uint8_t tx_msg[20]; + int tx_msg_len = 1; + tx_msg[0] = 0x80; // prefix + + for (int i = 0; i < buffer_len; i++) { + if (buffer[i] == 0x06 || buffer[i] == 0x0d || buffer[i] == 0x1b || buffer[i] == 0x40 || buffer[i] == 0x80) { + tx_msg[tx_msg_len++] = 0x1b; + tx_msg[tx_msg_len++] = buffer[i] ^ 0xff; + } else { + tx_msg[tx_msg_len++] = buffer[i]; + } + } + + tx_msg[tx_msg_len++] = 0x0D; // EOM + + this->write_array(tx_msg, tx_msg_len); +} + +void KamstrupKMPComponent::clear_uart_rx_buffer_() { + uint8_t tmp; + while (this->available()) { + this->read_byte(&tmp); + } +} + +void KamstrupKMPComponent::read_command_(uint16_t command) { + uint8_t buffer[20] = {0}; + int buffer_len = 0; + int data; + int timeout = 250; // ms + + // Read the data from the UART + while (timeout > 0) { + if (this->available()) { + data = this->read(); + if (data > -1) { + if (data == 0x40) { // start of message + buffer_len = 0; + } + buffer[buffer_len++] = (uint8_t) data; + if (data == 0x0D) { + break; + } + } else { + ESP_LOGE(TAG, "Error while reading from UART"); + } + } else { + delay(1); + timeout--; + } + } + + if (timeout == 0 || buffer_len == 0) { + ESP_LOGE(TAG, "Request timed out"); + return; + } + + // Validate message (prefix and suffix) + if (buffer[0] != 0x40) { + ESP_LOGE(TAG, "Received invalid message (prefix mismatch received 0x%02X, expected 0x40)", buffer[0]); + return; + } + + if (buffer[buffer_len - 1] != 0x0D) { + ESP_LOGE(TAG, "Received invalid message (EOM mismatch received 0x%02X, expected 0x0D)", buffer[buffer_len - 1]); + return; + } + + // Decode + uint8_t msg[20] = {0}; + int msg_len = 0; + for (int i = 1; i < buffer_len - 1; i++) { + if (buffer[i] == 0x1B) { + msg[msg_len++] = buffer[i + 1] ^ 0xFF; + i++; + } else { + msg[msg_len++] = buffer[i]; + } + } + + // Validate CRC + if (crc16_ccitt(msg, msg_len)) { + ESP_LOGE(TAG, "Received invalid message (CRC mismatch)"); + return; + } + + // All seems good. Now parse the message + this->parse_command_message_(command, msg, msg_len); +} + +void KamstrupKMPComponent::parse_command_message_(uint16_t command, const uint8_t *msg, int msg_len) { + // Validate the message + if (msg_len < 8) { + ESP_LOGE(TAG, "Received invalid message (message too small)"); + return; + } + + if (msg[0] != 0x3F || msg[1] != 0x10) { + ESP_LOGE(TAG, "Received invalid message (invalid header received 0x%02X%02X, expected 0x3F10)", msg[0], msg[1]); + return; + } + + uint16_t recv_command = msg[2] << 8 | msg[3]; + if (recv_command != command) { + ESP_LOGE(TAG, "Received invalid message (invalid unexpected command received 0x%04X, expected 0x%04X)", + recv_command, command); + return; + } + + uint8_t unit_idx = msg[4]; + uint8_t mantissa_range = msg[5]; + + if (mantissa_range > 4) { + ESP_LOGE(TAG, "Received invalid message (mantissa size too large %d, expected 4)", mantissa_range); + return; + } + + // Calculate exponent + float exponent = msg[6] & 0x3F; + if (msg[6] & 0x40) { + exponent = -exponent; + } + exponent = powf(10, exponent); + if (msg[6] & 0x80) { + exponent = -exponent; + } + + // Calculate mantissa + uint32_t mantissa = 0; + for (int i = 0; i < mantissa_range; i++) { + mantissa <<= 8; + mantissa |= msg[i + 7]; + } + + // Calculate the actual value + float value = mantissa * exponent; + + // Set sensor value + this->set_sensor_value_(command, value, unit_idx); +} + +void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint8_t unit_idx) { + const char *unit = UNITS[unit_idx]; + + // Standard sensors + if (command == CMD_HEAT_ENERGY && this->heat_energy_sensor_ != nullptr) { + this->heat_energy_sensor_->publish_state(value); + } else if (command == CMD_POWER && this->power_sensor_ != nullptr) { + this->power_sensor_->publish_state(value); + } else if (command == CMD_TEMP1 && this->temp1_sensor_ != nullptr) { + this->temp1_sensor_->publish_state(value); + } else if (command == CMD_TEMP2 && this->temp2_sensor_ != nullptr) { + this->temp2_sensor_->publish_state(value); + } else if (command == CMD_TEMP_DIFF && this->temp_diff_sensor_ != nullptr) { + this->temp_diff_sensor_->publish_state(value); + } else if (command == CMD_FLOW && this->flow_sensor_ != nullptr) { + this->flow_sensor_->publish_state(value); + } else if (command == CMD_VOLUME && this->volume_sensor_ != nullptr) { + this->volume_sensor_->publish_state(value); + } + + // Custom sensors + for (int i = 0; i < this->custom_commands_.size(); i++) { + if (command == this->custom_commands_[i]) { + this->custom_sensors_[i]->publish_state(value); + } + } + + ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value, unit); +} + +uint16_t crc16_ccitt(const uint8_t *buffer, int len) { + uint32_t poly = 0x1021; + uint32_t reg = 0x00; + for (int i = 0; i < len; i++) { + int mask = 0x80; + while (mask > 0) { + reg <<= 1; + if (buffer[i] & mask) { + reg |= 1; + } + mask >>= 1; + if (reg & 0x10000) { + reg &= 0xffff; + reg ^= poly; + } + } + } + return (uint16_t) reg; +} + +} // namespace kamstrup_kmp +} // namespace esphome diff --git a/esphome/components/kamstrup_kmp/kamstrup_kmp.h b/esphome/components/kamstrup_kmp/kamstrup_kmp.h new file mode 100644 index 0000000000..c9cc9c5a39 --- /dev/null +++ b/esphome/components/kamstrup_kmp/kamstrup_kmp.h @@ -0,0 +1,131 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace kamstrup_kmp { + +/* + =========================================================================== + === KAMSTRUP KMP === + =========================================================================== + + Kamstrup Meter Protocol (KMP) is a protocol used with Kamstrup district + heating meters, e.g. Kamstrup MULTICAL 403. + These devices register consumed heat from a district heating system. + It does this by measuring the incoming and outgoing water temperature + and by measuring the water flow. The temperature difference (delta T) + together with the water flow results in consumed energy, typically + in giga joule (GJ). + + The Kamstrup Multical has an optical interface just above the display. + This interface is essentially an RS-232 interface using a proprietary + protocol (Kamstrup Meter Protocol [KMP]). + + The integration uses this optical interface to periodically read the + configured values (sensors) from the meter. Supported sensors are: + - Heat Energy [GJ] + - Current Power Consumption [kW] + - Temperature 1 [°C] + - Temperature 2 [°C] + - Temperature Difference [°K] + - Water Flow [l/h] + - Volume [m3] + + Apart from these supported 'fixed' sensors, the user can configure up to + five custom sensors. The KMP command (16 bit unsigned int) has to be + provided in that case. + + Note: + The optical interface is enabled as soon as a button on the meter is pushed. + The interface stays active for a few minutes. To keep the interface 'alive' + magnets must be placed around the optical sensor. + + Units: + Units are set using the regular Sensor config in the user yaml. However, + KMP does also send the correct unit with every value. When DEBUG logging + is enabled, the received value with the received unit are logged. + + Acknowledgement: + This interface was inspired by: + - https://atomstar.tweakblogs.net/blog/19110/reading-out-kamstrup-multical-402-403-with-home-built-optical-head + - https://wiki.hal9k.dk/projects/kamstrup +*/ + +// KMP Commands +static const uint16_t CMD_HEAT_ENERGY = 0x003C; +static const uint16_t CMD_POWER = 0x0050; +static const uint16_t CMD_TEMP1 = 0x0056; +static const uint16_t CMD_TEMP2 = 0x0057; +static const uint16_t CMD_TEMP_DIFF = 0x0059; +static const uint16_t CMD_FLOW = 0x004A; +static const uint16_t CMD_VOLUME = 0x0044; + +// KMP units +static const char *const UNITS[] = { + "", "Wh", "kWh", "MWh", "GWh", "J", "kJ", "MJ", "GJ", "Cal", + "kCal", "Mcal", "Gcal", "varh", "kvarh", "Mvarh", "Gvarh", "VAh", "kVAh", "MVAh", + "GVAh", "kW", "kW", "MW", "GW", "kvar", "kvar", "Mvar", "Gvar", "VA", + "kVA", "MVA", "GVA", "V", "A", "kV", "kA", "C", "K", "l", + "m3", "l/h", "m3/h", "m3xC", "ton", "ton/h", "h", "hh:mm:ss", "yy:mm:dd", "yyyy:mm:dd", + "mm:dd", "", "bar", "RTC", "ASCII", "m3 x 10", "ton x 10", "GJ x 10", "minutes", "Bitfield", + "s", "ms", "days", "RTC-Q", "Datetime"}; + +class KamstrupKMPComponent : public PollingComponent, public uart::UARTDevice { + public: + void set_heat_energy_sensor(sensor::Sensor *sensor) { this->heat_energy_sensor_ = sensor; } + void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; } + void set_temp1_sensor(sensor::Sensor *sensor) { this->temp1_sensor_ = sensor; } + void set_temp2_sensor(sensor::Sensor *sensor) { this->temp2_sensor_ = sensor; } + void set_temp_diff_sensor(sensor::Sensor *sensor) { this->temp_diff_sensor_ = sensor; } + void set_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; } + void set_volume_sensor(sensor::Sensor *sensor) { this->volume_sensor_ = sensor; } + void dump_config() override; + float get_setup_priority() const override; + void update() override; + void loop() override; + void add_custom_sensor(sensor::Sensor *sensor, uint16_t command) { + this->custom_sensors_.push_back(sensor); + this->custom_commands_.push_back(command); + } + + protected: + // Sensors + sensor::Sensor *heat_energy_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *temp1_sensor_{nullptr}; + sensor::Sensor *temp2_sensor_{nullptr}; + sensor::Sensor *temp_diff_sensor_{nullptr}; + sensor::Sensor *flow_sensor_{nullptr}; + sensor::Sensor *volume_sensor_{nullptr}; + + // Custom sensors and commands + std::vector custom_sensors_; + std::vector custom_commands_; + + // Command queue + std::queue command_queue_; + + // Methods + + // Sends a command to the meter and receives its response + void send_command_(uint16_t command); + // Sends a message to the meter. A prefix/suffix and CRC are added + void send_message_(const uint8_t *msg, int msg_len); + // Clears and data that might be in the UART Rx buffer + void clear_uart_rx_buffer_(); + // Reads and validates the response to a send command + void read_command_(uint16_t command); + // Parses a received message + void parse_command_message_(uint16_t command, const uint8_t *msg, int msg_len); + // Sets the received value to the correct sensor + void set_sensor_value_(uint16_t command, float value, uint8_t unit_idx); +}; + +// "true" CCITT CRC-16 +uint16_t crc16_ccitt(const uint8_t *buffer, int len); + +} // namespace kamstrup_kmp +} // namespace esphome diff --git a/esphome/components/kamstrup_kmp/sensor.py b/esphome/components/kamstrup_kmp/sensor.py new file mode 100644 index 0000000000..c9024e4a2b --- /dev/null +++ b/esphome/components/kamstrup_kmp/sensor.py @@ -0,0 +1,132 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, uart +from esphome.const import ( + CONF_COMMAND, + CONF_CUSTOM, + CONF_FLOW, + CONF_ID, + CONF_POWER, + CONF_VOLUME, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLUME, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_CELSIUS, + UNIT_CUBIC_METER, + UNIT_EMPTY, + UNIT_KELVIN, + UNIT_KILOWATT, +) + +CODEOWNERS = ["@cfeenstra1024"] +DEPENDENCIES = ["uart"] + +kamstrup_kmp_ns = cg.esphome_ns.namespace("kamstrup_kmp") +KamstrupKMPComponent = kamstrup_kmp_ns.class_( + "KamstrupKMPComponent", cg.PollingComponent, uart.UARTDevice +) + +CONF_HEAT_ENERGY = "heat_energy" +CONF_TEMP1 = "temp1" +CONF_TEMP2 = "temp2" +CONF_TEMP_DIFF = "temp_diff" + +UNIT_GIGA_JOULE = "GJ" +UNIT_LITRE_PER_HOUR = "l/h" + +# Note: The sensor units are set automatically based un the received data from the meter +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(KamstrupKMPComponent), + cv.Optional(CONF_HEAT_ENERGY): sensor.sensor_schema( + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_GIGA_JOULE, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOWATT, + ), + cv.Optional(CONF_TEMP1): sensor.sensor_schema( + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + ), + cv.Optional(CONF_TEMP2): sensor.sensor_schema( + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + ), + cv.Optional(CONF_TEMP_DIFF): sensor.sensor_schema( + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KELVIN, + ), + cv.Optional(CONF_FLOW): sensor.sensor_schema( + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLUME, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_LITRE_PER_HOUR, + ), + cv.Optional(CONF_VOLUME): sensor.sensor_schema( + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLUME, + state_class=STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_CUBIC_METER, + ), + cv.Optional(CONF_CUSTOM): cv.ensure_list( + sensor.sensor_schema( + accuracy_decimals=1, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_EMPTY, + ).extend({cv.Required(CONF_COMMAND): cv.hex_uint16_t}) + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "kamstrup_kmp", baud_rate=1200, require_rx=True, require_tx=True +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + # Standard sensors + for key in [ + CONF_HEAT_ENERGY, + CONF_POWER, + CONF_TEMP1, + CONF_TEMP2, + CONF_TEMP_DIFF, + CONF_FLOW, + CONF_VOLUME, + ]: + if key not in config: + continue + conf = config[key] + sens = await sensor.new_sensor(conf) + cg.add(getattr(var, f"set_{key}_sensor")(sens)) + + # Custom sensors + if CONF_CUSTOM in config: + for conf in config[CONF_CUSTOM]: + sens = await sensor.new_sensor(conf) + cg.add(var.add_custom_sensor(sens, conf[CONF_COMMAND])) diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index bda1764cfc..58c9a289a3 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -40,9 +40,9 @@ There are three documented parameters for modes: 00 04 = Energy output mode This mode outputs detailed signal energy values for each gate and the target distance. The data format consist of the following. - Header HH, Length LL, Persence PP, Distance DD, Range Gate GG, 16 Gate Energies EE, Footer FF - HH HH HH HH LL LL PP DD DD GG GG EE EE .. 16x .. FF FF FF FF - F4 F3 F2 F1 00 23 00 00 00 00 01 00 00 .. .. .. .. F8 F7 F6 F5 + Header HH, Length LL, Persence PP, Distance DD, 16 Gate Energies EE, Footer FF + HH HH HH HH LL LL PP DD DD EE EE .. 16x .. FF FF FF FF + F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5 00 00 = debug output mode This mode outputs detailed values consisting of 20 Dopplers, 16 Ranges for a total 20 * 16 * 4 bytes The data format consist of the following. @@ -211,10 +211,11 @@ void LD2420Component::factory_reset_action() { void LD2420Component::restart_module_action() { ESP_LOGCONFIG(TAG, "Restarting LD2420 module..."); this->send_module_restart(); - delay_microseconds_safe(45000); - this->set_config_mode(true); - this->set_system_mode(system_mode_); - this->set_config_mode(false); + this->set_timeout(250, [this]() { + this->set_config_mode(true); + this->set_system_mode(system_mode_); + this->set_config_mode(false); + }); ESP_LOGCONFIG(TAG, "LD2420 Restarted."); } @@ -527,18 +528,16 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { this->write_byte(cmd_buffer[index]); } - delay_microseconds_safe(500); // give the module a moment to process it error = 0; if (frame.command == CMD_RESTART) { - delay_microseconds_safe(25000); // Wait for the restart - return 0; // restart does not reply exit now + return 0; // restart does not reply exit now } while (!this->cmd_reply_.ack) { while (available()) { this->readline_(read(), ack_buffer, sizeof(ack_buffer)); } - delay_microseconds_safe(250); + delay_microseconds_safe(1450); if (loop_count <= 0) { error = LD2420_ERROR_TIMEOUT; retry--; diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index dfb84c1e76..0533143d37 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -96,6 +96,12 @@ esp_err_t configure_timer_frequency(ledc_mode_t speed_mode, ledc_timer_t timer_n } #endif +#ifdef USE_ESP_IDF +constexpr int ledc_angle_to_htop(float angle, uint8_t bit_depth) { + return static_cast(angle * ((1U << bit_depth) - 1) / 360.); +} +#endif // USE_ESP_IDF + void LEDCOutput::write_state(float state) { if (!initialized_) { ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!"); @@ -117,7 +123,8 @@ void LEDCOutput::write_state(float state) { #ifdef USE_ESP_IDF auto speed_mode = get_speed_mode(channel_); auto chan_num = static_cast(channel_ % 8); - ledc_set_duty(speed_mode, chan_num, duty); + int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_); + ledc_set_duty_with_hpoint(speed_mode, chan_num, duty, hpoint); ledc_update_duty(speed_mode, chan_num); #endif } @@ -143,8 +150,10 @@ void LEDCOutput::setup() { this->status_set_error(); return; } + int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_); ESP_LOGV(TAG, "Configured frequency %f with a bit depth of %u bits", this->frequency_, this->bit_depth_); + ESP_LOGV(TAG, "Angle of %.1f° results in hpoint %u", this->phase_angle_, hpoint); ledc_channel_config_t chan_conf{}; chan_conf.gpio_num = pin_->get_pin(); @@ -153,7 +162,7 @@ void LEDCOutput::setup() { chan_conf.intr_type = LEDC_INTR_DISABLE; chan_conf.timer_sel = timer_num; chan_conf.duty = inverted_ == pin_->is_inverted() ? 0 : (1U << bit_depth_); - chan_conf.hpoint = 0; + chan_conf.hpoint = hpoint; ledc_channel_config(&chan_conf); initialized_ = true; this->status_clear_error(); @@ -165,6 +174,7 @@ void LEDCOutput::dump_config() { LOG_PIN(" Pin ", this->pin_); ESP_LOGCONFIG(TAG, " LEDC Channel: %u", this->channel_); ESP_LOGCONFIG(TAG, " PWM Frequency: %.1f Hz", this->frequency_); + ESP_LOGCONFIG(TAG, " Phase angle: %.1f°", this->phase_angle_); ESP_LOGCONFIG(TAG, " Bit depth: %u", this->bit_depth_); ESP_LOGV(TAG, " Max frequency for bit depth: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_)); ESP_LOGV(TAG, " Min frequency for bit depth: %f", diff --git a/esphome/components/ledc/ledc_output.h b/esphome/components/ledc/ledc_output.h index a78bf440a9..f04543bc5b 100644 --- a/esphome/components/ledc/ledc_output.h +++ b/esphome/components/ledc/ledc_output.h @@ -19,6 +19,7 @@ class LEDCOutput : public output::FloatOutput, public Component { void set_channel(uint8_t channel) { this->channel_ = channel; } void set_frequency(float frequency) { this->frequency_ = frequency; } + void set_phase_angle(float angle) { this->phase_angle_ = angle; } /// Dynamically change frequency at runtime void update_frequency(float frequency) override; @@ -35,6 +36,7 @@ class LEDCOutput : public output::FloatOutput, public Component { InternalGPIOPin *pin_; uint8_t channel_{}; uint8_t bit_depth_{}; + float phase_angle_{0.0f}; float frequency_{}; float duty_{0.0f}; bool initialized_ = false; diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index f6dc89cd9b..32c68f8d24 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -3,6 +3,7 @@ from esphome.components import output import esphome.config_validation as cv import esphome.codegen as cg from esphome.const import ( + CONF_PHASE_ANGLE, CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, @@ -46,6 +47,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency, cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15), + cv.Optional(CONF_PHASE_ANGLE): cv.All( + cv.only_with_esp_idf, cv.angle, cv.float_range(min=0.0, max=360.0) + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -58,6 +62,8 @@ async def to_code(config): if CONF_CHANNEL in config: cg.add(var.set_channel(config[CONF_CHANNEL])) cg.add(var.set_frequency(config[CONF_FREQUENCY])) + if CONF_PHASE_ANGLE in config: + cg.add(var.set_phase_angle(config[CONF_PHASE_ANGLE])) @automation.register_action( diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index e36c08d522..34aff0ae16 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -170,7 +170,7 @@ def _notify_old_style(config): ARDUINO_VERSIONS = { "dev": (cv.Version(0, 0, 0), "https://github.com/libretiny-eu/libretiny.git"), "latest": (cv.Version(0, 0, 0), None), - "recommended": (cv.Version(1, 4, 1), None), + "recommended": (cv.Version(1, 5, 1), None), } @@ -309,7 +309,7 @@ async def component_to_code(config): lt_options["LT_UART_SILENT_ENABLED"] = 0 lt_options["LT_UART_SILENT_ALL"] = 0 # set default UART port - if uart_port := framework.get(CONF_UART_PORT, None) is not None: + if (uart_port := framework.get(CONF_UART_PORT, None)) is not None: lt_options["LT_UART_DEFAULT_PORT"] = uart_port # add custom options lt_options.update(framework[CONF_OPTIONS]) diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index c2109b2d23..73083a58b7 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -100,6 +100,7 @@ struct AddressableColorWipeEffectColor { uint8_t r, g, b, w; bool random; size_t num_leds; + bool gradient; }; class AddressableColorWipeEffect : public AddressableLightEffect { @@ -117,8 +118,15 @@ class AddressableColorWipeEffect : public AddressableLightEffect { it.shift_left(1); else it.shift_right(1); - const AddressableColorWipeEffectColor color = this->colors_[this->at_color_]; - const Color esp_color = Color(color.r, color.g, color.b, color.w); + const AddressableColorWipeEffectColor &color = this->colors_[this->at_color_]; + Color esp_color = Color(color.r, color.g, color.b, color.w); + if (color.gradient) { + size_t next_color_index = (this->at_color_ + 1) % this->colors_.size(); + const AddressableColorWipeEffectColor &next_color = this->colors_[next_color_index]; + const Color next_esp_color = Color(next_color.r, next_color.g, next_color.b, next_color.w); + uint8_t gradient = 255 * ((float) this->leds_added_ / color.num_leds); + esp_color = esp_color.gradient(next_esp_color, gradient); + } if (this->reverse_) it[-1] = esp_color; else diff --git a/esphome/components/light/effects.py b/esphome/components/light/effects.py index 5212e90938..5093ace949 100644 --- a/esphome/components/light/effects.py +++ b/esphome/components/light/effects.py @@ -58,6 +58,7 @@ from .types import ( CONF_ADD_LED_INTERVAL = "add_led_interval" CONF_REVERSE = "reverse" +CONF_GRADIENT = "gradient" CONF_MOVE_INTERVAL = "move_interval" CONF_SCAN_WIDTH = "scan_width" CONF_TWINKLE_PROBABILITY = "twinkle_probability" @@ -386,6 +387,7 @@ async def addressable_rainbow_effect_to_code(config, effect_id): cv.Optional(CONF_WHITE, default=1.0): cv.percentage, cv.Optional(CONF_RANDOM, default=False): cv.boolean, cv.Required(CONF_NUM_LEDS): cv.All(cv.uint32_t, cv.Range(min=1)), + cv.Optional(CONF_GRADIENT, default=False): cv.boolean, } ), cv.Optional( @@ -409,6 +411,7 @@ async def addressable_color_wipe_effect_to_code(config, effect_id): ("w", int(round(color[CONF_WHITE] * 255))), ("random", color[CONF_RANDOM]), ("num_leds", color[CONF_NUM_LEDS]), + ("gradient", color[CONF_GRADIENT]), ) ) cg.add(var.set_colors(colors)) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 50ebd8882b..fe6538e65e 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -120,6 +120,7 @@ void LightState::loop() { // Apply transformer (if any) if (this->transformer_ != nullptr) { auto values = this->transformer_->apply(); + this->is_transformer_active_ = true; if (values.has_value()) { this->current_values = *values; this->output_->update_state(this); @@ -131,6 +132,7 @@ void LightState::loop() { this->current_values = this->transformer_->get_target_values(); this->transformer_->stop(); + this->is_transformer_active_ = false; this->transformer_ = nullptr; this->target_state_reached_callback_.call(); } @@ -214,6 +216,8 @@ void LightState::current_values_as_ct(float *color_temperature, float *white_bri this->gamma_correct_); } +bool LightState::is_transformer_active() { return this->is_transformer_active_; } + void LightState::start_effect_(uint32_t effect_index) { this->stop_effect_(); if (effect_index == 0) @@ -263,6 +267,7 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b } void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) { + this->is_transformer_active_ = false; this->transformer_ = nullptr; this->current_values = target; if (set_remote_values) { diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index ac4718ade5..b0aaa453b5 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -144,6 +144,17 @@ class LightState : public EntityBase, public Component { void current_values_as_ct(float *color_temperature, float *white_brightness); + /** + * Indicator if a transformer (e.g. transition) is active. This is useful + * for effects e.g. at the start of the apply() method, add a check like: + * + * if (this->state_->is_transformer_active()) { + * // Something is already running. + * return; + * } + */ + bool is_transformer_active(); + protected: friend LightOutput; friend LightCall; @@ -203,6 +214,9 @@ class LightState : public EntityBase, public Component { LightRestoreMode restore_mode_; /// List of effects for this light. std::vector effects_; + + // for effects, true if a transformer (transition) is active. + bool is_transformer_active_ = false; }; } // namespace light diff --git a/esphome/components/lightwaverf/LwTx.h b/esphome/components/lightwaverf/LwTx.h index 719826640e..fe7b942a3a 100644 --- a/esphome/components/lightwaverf/LwTx.h +++ b/esphome/components/lightwaverf/LwTx.h @@ -3,6 +3,8 @@ #include "esphome/core/component.h" #include "esphome/core/hal.h" +#include + namespace esphome { namespace lightwaverf { diff --git a/esphome/components/lilygo_t5_47/touchscreen/__init__.py b/esphome/components/lilygo_t5_47/touchscreen/__init__.py index 01b03c807f..17f7262785 100644 --- a/esphome/components/lilygo_t5_47/touchscreen/__init__.py +++ b/esphome/components/lilygo_t5_47/touchscreen/__init__.py @@ -18,7 +18,7 @@ LilygoT547Touchscreen = lilygo_t5_47_ns.class_( CONF_LILYGO_T5_47_TOUCHSCREEN_ID = "lilygo_t5_47_touchscreen_id" -CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( +CONFIG_SCHEMA = touchscreen.touchscreen_schema("250ms").extend( cv.Schema( { cv.GenerateID(): cv.declare_id(LilygoT547Touchscreen), diff --git a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp index 64cc7ad4d1..58f2a42812 100644 --- a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp +++ b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp @@ -38,9 +38,14 @@ void LilygoT547Touchscreen::setup() { } this->write_register(POWER_REGISTER, WAKEUP_CMD, 1); - - this->x_raw_max_ = this->get_width_(); - this->y_raw_max_ = this->get_height_(); + if (this->display_ != nullptr) { + if (this->x_raw_max_ == this->x_raw_min_) { + this->x_raw_max_ = this->display_->get_native_width(); + } + if (this->y_raw_max_ == this->y_raw_min_) { + this->x_raw_max_ = this->display_->get_native_height(); + } + } } void LilygoT547Touchscreen::update_touches() { diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 6cad783db9..c05f3d54aa 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -84,7 +84,7 @@ UART_SELECTION_ESP32 = { VARIANT_ESP32: [UART0, UART1, UART2], VARIANT_ESP32S2: [UART0, UART1, USB_CDC], VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], - VARIANT_ESP32C3: [UART0, UART1, USB_SERIAL_JTAG], + VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32C2: [UART0, UART1], VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], @@ -127,6 +127,10 @@ def uart_selection(value): if CORE.using_arduino and value.upper() in ESP_ARDUINO_UNSUPPORTED_USB_UARTS: raise cv.Invalid(f"Arduino framework does not support {value}.") variant = get_esp32_variant() + if CORE.using_esp_idf and variant == VARIANT_ESP32C3 and value == USB_CDC: + raise cv.Invalid( + f"{value} is not supported for variant {variant} when using ESP-IDF." + ) if variant in UART_SELECTION_ESP32: return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value) if CORE.is_esp8266: @@ -140,6 +144,8 @@ def uart_selection(value): component = get_libretiny_component() if component in UART_SELECTION_LIBRETINY: return cv.one_of(*UART_SELECTION_LIBRETINY[component], upper=True)(value) + if CORE.is_host: + raise cv.Invalid("Uart selection not valid for host platform") raise NotImplementedError @@ -172,9 +178,10 @@ CONFIG_SCHEMA = cv.All( esp8266=UART0, esp32=UART0, esp32_s2=USB_CDC, - esp32_s3_idf=USB_SERIAL_JTAG, - esp32_c3_idf=USB_SERIAL_JTAG, esp32_s3_arduino=USB_CDC, + esp32_s3_idf=USB_SERIAL_JTAG, + esp32_c3_arduino=USB_CDC, + esp32_c3_idf=USB_SERIAL_JTAG, rp2040=USB_CDC, bk72xx=DEFAULT, rtl87xx=DEFAULT, @@ -265,12 +272,24 @@ async def to_code(config): if CORE.using_arduino: if config[CONF_HARDWARE_UART] == USB_CDC: cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1") + if CORE.is_esp32 and get_esp32_variant() == VARIANT_ESP32C3: + cg.add_build_flag("-DARDUINO_USB_MODE=1") if CORE.using_esp_idf: if config[CONF_HARDWARE_UART] == USB_CDC: add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True) elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG: add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True) + try: + uart_selection(USB_SERIAL_JTAG) + cg.add_define("USE_LOGGER_USB_SERIAL_JTAG") + except cv.Invalid: + pass + try: + uart_selection(USB_CDC) + cg.add_define("USE_LOGGER_USB_CDC") + except cv.Invalid: + pass # Register at end for safe mode await cg.register_component(log, config) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 05b97a5f64..166fe2693d 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -1,30 +1,9 @@ #include "logger.h" #include -#ifdef USE_ESP_IDF -#include - -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \ - defined(USE_ESP32_VARIANT_ESP32H2) -#include -#include -#include -#endif - -#include "freertos/FreeRTOS.h" -#include "esp_idf_version.h" - -#include -#include -#include - -#endif // USE_ESP_IDF - -#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) -#include -#endif // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/core/application.h" namespace esphome { namespace logger { @@ -106,58 +85,6 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr } #endif -#ifdef USE_ESP_IDF -void Logger::init_uart_() { - uart_config_t uart_config{}; - uart_config.baud_rate = (int) baud_rate_; - uart_config.data_bits = UART_DATA_8_BITS; - uart_config.parity = UART_PARITY_DISABLE; - uart_config.stop_bits = UART_STOP_BITS_1; - uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) - uart_config.source_clk = UART_SCLK_DEFAULT; -#endif - uart_param_config(this->uart_num_, &uart_config); - const int uart_buffer_size = tx_buffer_size_; - // Install UART driver using an event queue here - uart_driver_install(this->uart_num_, uart_buffer_size, uart_buffer_size, 10, nullptr, 0); -} - -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) -void Logger::init_usb_cdc_() {} -#endif - -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \ - defined(USE_ESP32_VARIANT_ESP32H2) -void Logger::init_usb_serial_jtag_() { - setvbuf(stdin, NULL, _IONBF, 0); // Disable buffering on stdin - - // Minicom, screen, idf_monitor send CR when ENTER key is pressed - esp_vfs_dev_usb_serial_jtag_set_rx_line_endings(ESP_LINE_ENDINGS_CR); - // Move the caret to the beginning of the next line on '\n' - esp_vfs_dev_usb_serial_jtag_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); - - // Enable non-blocking mode on stdin and stdout - fcntl(fileno(stdout), F_SETFL, 0); - fcntl(fileno(stdin), F_SETFL, 0); - - usb_serial_jtag_driver_config_t usb_serial_jtag_config{}; - usb_serial_jtag_config.rx_buffer_size = 512; - usb_serial_jtag_config.tx_buffer_size = 512; - - esp_err_t ret = ESP_OK; - // Install USB-SERIAL-JTAG driver for interrupt-driven reads and writes - ret = usb_serial_jtag_driver_install(&usb_serial_jtag_config); - if (ret != ESP_OK) { - return; - } - - // Tell vfs to use usb-serial-jtag driver - esp_vfs_usb_serial_jtag_use_driver(); -} -#endif -#endif - int HOT Logger::level_for(const char *tag) { // Uses std::vector<> for low memory footprint, though the vector // could be sorted to minimize lookup times. This feature isn't used that @@ -169,6 +96,7 @@ int HOT Logger::level_for(const char *tag) { } return ESPHOME_LOG_LEVEL; } + void HOT Logger::log_message_(int level, const char *tag, int offset) { // remove trailing newline if (this->tx_buffer_[this->tx_buffer_at_ - 1] == '\n') { @@ -178,28 +106,9 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { this->set_null_terminator_(); const char *msg = this->tx_buffer_ + offset; + if (this->baud_rate_ > 0) { -#ifdef USE_ARDUINO - this->hw_serial_->println(msg); -#endif // USE_ARDUINO -#ifdef USE_ESP_IDF - if ( -#if defined(USE_ESP32_VARIANT_ESP32S2) - this->uart_ == UART_SELECTION_USB_CDC -#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) - this->uart_ == UART_SELECTION_USB_SERIAL_JTAG -#elif defined(USE_ESP32_VARIANT_ESP32S3) - this->uart_ == UART_SELECTION_USB_CDC || this->uart_ == UART_SELECTION_USB_SERIAL_JTAG -#else - /* DISABLES CODE */ (false) // NOLINT -#endif - ) { - puts(msg); - } else { - uart_write_bytes(this->uart_num_, msg, strlen(msg)); - uart_write_bytes(this->uart_num_, "\n", 1); - } -#endif + this->write_msg_(msg); } #ifdef USE_ESP32 @@ -211,9 +120,6 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { if (xPortGetFreeHeapSize() < 2048) return; #endif -#ifdef USE_HOST - puts(msg); -#endif this->log_callback_.call(level, tag, msg); } @@ -223,181 +129,23 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT } -#ifndef USE_LIBRETINY -void Logger::pre_setup() { - if (this->baud_rate_ > 0) { +#ifdef USE_LOGGER_USB_CDC +void Logger::loop() { #ifdef USE_ARDUINO - switch (this->uart_) { - case UART_SELECTION_UART0: -#ifdef USE_ESP8266 - case UART_SELECTION_UART0_SWAP: -#endif -#ifdef USE_RP2040 - this->hw_serial_ = &Serial1; - Serial1.begin(this->baud_rate_); -#else -#if ARDUINO_USB_CDC_ON_BOOT - this->hw_serial_ = &Serial0; - Serial0.begin(this->baud_rate_); -#else - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); -#endif -#endif -#ifdef USE_ESP8266 - if (this->uart_ == UART_SELECTION_UART0_SWAP) { - Serial.swap(); - } - Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); -#endif - break; - case UART_SELECTION_UART1: -#ifdef USE_RP2040 - this->hw_serial_ = &Serial2; - Serial2.begin(this->baud_rate_); -#else - this->hw_serial_ = &Serial1; - Serial1.begin(this->baud_rate_); -#endif -#ifdef USE_ESP8266 - Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); -#endif - break; -#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ - !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) - case UART_SELECTION_UART2: - this->hw_serial_ = &Serial2; - Serial2.begin(this->baud_rate_); - break; -#endif -#if defined(USE_ESP32) && \ - (defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3)) -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - case UART_SELECTION_USB_CDC: -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) - case UART_SELECTION_USB_SERIAL_JTAG: -#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3 -#ifdef USE_ESP32_VARIANT_ESP32C3 - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); -#endif // USE_ESP32_VARIANT_ESP32C3 -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) -#if ARDUINO_USB_CDC_ON_BOOT - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); -#else - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); -#endif // ARDUINO_USB_CDC_ON_BOOT -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 - break; -#endif // USE_ESP32 && (USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3) -#ifdef USE_RP2040 - case UART_SELECTION_USB_CDC: - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); - break; -#endif // USE_RP2040 - } -#endif // USE_ARDUINO -#ifdef USE_ESP_IDF - this->uart_num_ = UART_NUM_0; - switch (this->uart_) { - case UART_SELECTION_UART0: - this->uart_num_ = UART_NUM_0; - break; - case UART_SELECTION_UART1: - this->uart_num_ = UART_NUM_1; - break; -#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ - !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) && !defined(USE_ESP32_VARIANT_ESP32H2) - case UART_SELECTION_UART2: - this->uart_num_ = UART_NUM_2; - break; -#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 && - // !USE_ESP32_VARIANT_ESP32H2 -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - case UART_SELECTION_USB_CDC: - this->uart_num_ = -1; - this->init_usb_cdc_(); - break; -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \ - defined(USE_ESP32_VARIANT_ESP32H2) - case UART_SELECTION_USB_SERIAL_JTAG: - this->uart_num_ = -1; - this->init_usb_serial_jtag_(); - break; -#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 || - // USE_ESP32_VARIANT_ESP32H2 - } - if (this->uart_num_ >= 0) { - this->init_uart_(); - } -#endif // USE_ESP_IDF + if (this->uart_ != UART_SELECTION_USB_CDC) { + return; } -#ifdef USE_ESP8266 - else { - uart_set_debug(UART_NO); + static bool opened = false; + if (opened == Serial) { + return; } -#endif // USE_ESP8266 - - global_logger = this; -#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO) - esp_log_set_vprintf(esp_idf_log_vprintf_); - if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { - esp_log_level_set("*", ESP_LOG_VERBOSE); + if (false == opened) { + App.schedule_dump_config(); } -#endif // USE_ESP_IDF || USE_ESP32_FRAMEWORK_ARDUINO - - ESP_LOGI(TAG, "Log initialized"); + opened = !opened; +#endif } -#else // USE_LIBRETINY -void Logger::pre_setup() { - if (this->baud_rate_ > 0) { - switch (this->uart_) { -#if LT_HW_UART0 - case UART_SELECTION_UART0: - this->hw_serial_ = &Serial0; - Serial0.begin(this->baud_rate_); - break; #endif -#if LT_HW_UART1 - case UART_SELECTION_UART1: - this->hw_serial_ = &Serial1; - Serial1.begin(this->baud_rate_); - break; -#endif -#if LT_HW_UART2 - case UART_SELECTION_UART2: - this->hw_serial_ = &Serial2; - Serial2.begin(this->baud_rate_); - break; -#endif - default: - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); - if (this->uart_ != UART_SELECTION_DEFAULT) { - ESP_LOGW(TAG, " The chosen logger UART port is not available on this board." - "The default port was used instead."); - } - break; - } - - // change lt_log() port to match default Serial - if (this->uart_ == UART_SELECTION_DEFAULT) { - this->uart_ = (UARTSelection) (LT_UART_DEFAULT_SERIAL + 1); - lt_log_set_port(LT_UART_DEFAULT_SERIAL); - } else { - lt_log_set_port(this->uart_ - 1); - } - } - - global_logger = this; - ESP_LOGI(TAG, "Log initialized"); -} -#endif // USE_LIBRETINY void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } void Logger::set_log_level(const std::string &tag, int log_level) { @@ -413,37 +161,13 @@ void Logger::add_on_log_callback(std::functionbaud_rate_); -#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) - ESP_LOGCONFIG(TAG, " Hardware UART: %s", UART_SELECTIONS[this->uart_]); + ESP_LOGCONFIG(TAG, " Hardware UART: %s", get_uart_selection_()); #endif for (auto &it : this->log_levels_) { diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 68efc056df..f6c1574ffb 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -34,40 +34,31 @@ enum UARTSelection { #ifdef USE_LIBRETINY UART_SELECTION_DEFAULT = 0, UART_SELECTION_UART0, - UART_SELECTION_UART1, - UART_SELECTION_UART2, #else UART_SELECTION_UART0 = 0, +#endif UART_SELECTION_UART1, -#if defined(USE_ESP32) -#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ - !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) && !defined(USE_ESP32_VARIANT_ESP32H2) +#if defined(USE_LIBRETINY) || defined(USE_ESP32_VARIANT_ESP32) UART_SELECTION_UART2, -#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 && - // !USE_ESP32_VARIANT_ESP32S3 && !USE_ESP32_VARIANT_ESP32H2 -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#endif +#ifdef USE_LOGGER_USB_CDC UART_SELECTION_USB_CDC, -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \ - defined(USE_ESP32_VARIANT_ESP32H2) +#endif +#ifdef USE_LOGGER_USB_SERIAL_JTAG UART_SELECTION_USB_SERIAL_JTAG, -#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 || - // USE_ESP32_VARIANT_ESP32H2 -#endif // USE_ESP32 +#endif #ifdef USE_ESP8266 UART_SELECTION_UART0_SWAP, #endif // USE_ESP8266 -#ifdef USE_RP2040 - UART_SELECTION_USB_CDC, -#endif // USE_RP2040 -#endif // USE_LIBRETINY }; #endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY class Logger : public Component { public: explicit Logger(uint32_t baud_rate, size_t tx_buffer_size); - +#ifdef USE_LOGGER_USB_CDC + void loop() override; +#endif /// Manually set the baud rate for serial, set to 0 to disable. void set_baud_rate(uint32_t baud_rate); uint32_t get_baud_rate() const { return baud_rate_; } @@ -105,19 +96,10 @@ class Logger : public Component { #endif protected: -#ifdef USE_ESP_IDF - void init_uart_(); -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - void init_usb_cdc_(); -#endif -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \ - defined(USE_ESP32_VARIANT_ESP32H2) - void init_usb_serial_jtag_(); -#endif -#endif void write_header_(int level, const char *tag, int line); void write_footer_(); void log_message_(int level, const char *tag, int offset = 0); + void write_msg_(const char *msg); inline bool is_buffer_full_() const { return this->tx_buffer_at_ >= this->tx_buffer_size_; } inline int buffer_remaining_capacity_() const { return this->tx_buffer_size_ - this->tx_buffer_at_; } @@ -157,6 +139,10 @@ class Logger : public Component { va_end(arg); } +#ifndef USE_HOST + const char *get_uart_selection_(); +#endif + uint32_t baud_rate_; char *tx_buffer_{nullptr}; int tx_buffer_at_{0}; diff --git a/esphome/components/logger/logger_esp32.cpp b/esphome/components/logger/logger_esp32.cpp new file mode 100644 index 0000000000..740e086f92 --- /dev/null +++ b/esphome/components/logger/logger_esp32.cpp @@ -0,0 +1,201 @@ +#ifdef USE_ESP32 +#include "logger.h" + +#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) +#include +#endif // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF + +#ifdef USE_ESP_IDF +#include + +#ifdef USE_LOGGER_USB_SERIAL_JTAG +#include +#include +#include +#endif + +#include "freertos/FreeRTOS.h" +#include "esp_idf_version.h" + +#include +#include +#include + +#endif // USE_ESP_IDF + +#include "esphome/core/log.h" + +namespace esphome { +namespace logger { + +static const char *const TAG = "logger"; + +#ifdef USE_ESP_IDF + +#ifdef USE_LOGGER_USB_SERIAL_JTAG +static void init_usb_serial_jtag_() { + setvbuf(stdin, NULL, _IONBF, 0); // Disable buffering on stdin + + // Minicom, screen, idf_monitor send CR when ENTER key is pressed + esp_vfs_dev_usb_serial_jtag_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + // Move the caret to the beginning of the next line on '\n' + esp_vfs_dev_usb_serial_jtag_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + + // Enable non-blocking mode on stdin and stdout + fcntl(fileno(stdout), F_SETFL, 0); + fcntl(fileno(stdin), F_SETFL, 0); + + usb_serial_jtag_driver_config_t usb_serial_jtag_config{}; + usb_serial_jtag_config.rx_buffer_size = 512; + usb_serial_jtag_config.tx_buffer_size = 512; + + esp_err_t ret = ESP_OK; + // Install USB-SERIAL-JTAG driver for interrupt-driven reads and writes + ret = usb_serial_jtag_driver_install(&usb_serial_jtag_config); + if (ret != ESP_OK) { + return; + } + + // Tell vfs to use usb-serial-jtag driver + esp_vfs_usb_serial_jtag_use_driver(); +} +#endif + +void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_size) { + uart_config_t uart_config{}; + uart_config.baud_rate = (int) baud_rate; + uart_config.data_bits = UART_DATA_8_BITS; + uart_config.parity = UART_PARITY_DISABLE; + uart_config.stop_bits = UART_STOP_BITS_1; + uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + uart_config.source_clk = UART_SCLK_DEFAULT; +#endif + uart_param_config(uart_num, &uart_config); + const int uart_buffer_size = tx_buffer_size; + // Install UART driver using an event queue here + uart_driver_install(uart_num, uart_buffer_size, uart_buffer_size, 10, nullptr, 0); +} + +#endif // USE_ESP_IDF + +void Logger::pre_setup() { + if (this->baud_rate_ > 0) { +#ifdef USE_ARDUINO + switch (this->uart_) { + case UART_SELECTION_UART0: +#if ARDUINO_USB_CDC_ON_BOOT + this->hw_serial_ = &Serial0; + Serial0.begin(this->baud_rate_); +#else + this->hw_serial_ = &Serial; + Serial.begin(this->baud_rate_); +#endif + break; + case UART_SELECTION_UART1: + this->hw_serial_ = &Serial1; + Serial1.begin(this->baud_rate_); + break; +#ifdef USE_ESP32_VARIANT_ESP32 + case UART_SELECTION_UART2: + this->hw_serial_ = &Serial2; + Serial2.begin(this->baud_rate_); + break; +#endif + +#ifdef USE_LOGGER_USB_CDC + case UART_SELECTION_USB_CDC: + this->hw_serial_ = &Serial; +#if ARDUINO_USB_CDC_ON_BOOT + Serial.setTxTimeoutMs(0); // workaround for 2.0.9 crash when there's no data connection +#endif + Serial.begin(this->baud_rate_); + break; +#endif + } +#endif // USE_ARDUINO + +#ifdef USE_ESP_IDF + this->uart_num_ = UART_NUM_0; + switch (this->uart_) { + case UART_SELECTION_UART0: + this->uart_num_ = UART_NUM_0; + break; + case UART_SELECTION_UART1: + this->uart_num_ = UART_NUM_1; + break; +#ifdef USE_ESP32_VARIANT_ESP32 + case UART_SELECTION_UART2: + this->uart_num_ = UART_NUM_2; + break; +#endif +#ifdef USE_LOGGER_USB_CDC + case UART_SELECTION_USB_CDC: + this->uart_num_ = -1; + break; +#endif +#ifdef USE_LOGGER_USB_SERIAL_JTAG + case UART_SELECTION_USB_SERIAL_JTAG: + this->uart_num_ = -1; + init_usb_serial_jtag_(); + break; +#endif + } + if (this->uart_num_ >= 0) { + init_uart(this->uart_num_, baud_rate_, tx_buffer_size_); + } +#endif // USE_ESP_IDF + } + + global_logger = this; +#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO) + esp_log_set_vprintf(esp_idf_log_vprintf_); + if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { + esp_log_level_set("*", ESP_LOG_VERBOSE); + } +#endif // USE_ESP_IDF || USE_ESP32_FRAMEWORK_ARDUINO + + ESP_LOGI(TAG, "Log initialized"); +} + +#ifdef USE_ESP_IDF +void HOT Logger::write_msg_(const char *msg) { + if ( +#if defined(USE_ESP32_VARIANT_ESP32S2) + this->uart_ == UART_SELECTION_USB_CDC +#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) + this->uart_ == UART_SELECTION_USB_SERIAL_JTAG +#elif defined(USE_ESP32_VARIANT_ESP32S3) + this->uart_ == UART_SELECTION_USB_CDC || this->uart_ == UART_SELECTION_USB_SERIAL_JTAG +#else + /* DISABLES CODE */ (false) // NOLINT +#endif + ) { + puts(msg); + } else { + uart_write_bytes(this->uart_num_, msg, strlen(msg)); + uart_write_bytes(this->uart_num_, "\n", 1); + } +} +#else +void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } +#endif + +const char *const UART_SELECTIONS[] = { + "UART0", "UART1", +#ifdef USE_ESP32_VARIANT_ESP32 + "UART2", +#endif +#ifdef USE_LOGGER_USB_CDC + "USB_CDC", +#endif +#ifdef USE_LOGGER_USB_SERIAL_JTAG + "USB_SERIAL_JTAG", +#endif +}; + +const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } + +} // namespace logger +} // namespace esphome +#endif diff --git a/esphome/components/logger/logger_esp8266.cpp b/esphome/components/logger/logger_esp8266.cpp new file mode 100644 index 0000000000..5bfeb21007 --- /dev/null +++ b/esphome/components/logger/logger_esp8266.cpp @@ -0,0 +1,45 @@ +#ifdef USE_ESP8266 +#include "logger.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace logger { + +static const char *const TAG = "logger"; + +void Logger::pre_setup() { + if (this->baud_rate_ > 0) { + switch (this->uart_) { + case UART_SELECTION_UART0: + case UART_SELECTION_UART0_SWAP: + this->hw_serial_ = &Serial; + Serial.begin(this->baud_rate_); + if (this->uart_ == UART_SELECTION_UART0_SWAP) { + Serial.swap(); + } + Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); + break; + case UART_SELECTION_UART1: + this->hw_serial_ = &Serial1; + Serial1.begin(this->baud_rate_); + Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); + break; + } + } else { + uart_set_debug(UART_NO); + } + + global_logger = this; + + ESP_LOGI(TAG, "Log initialized"); +} + +void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } + +const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"}; + +const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } + +} // namespace logger +} // namespace esphome +#endif diff --git a/esphome/components/logger/logger_host.cpp b/esphome/components/logger/logger_host.cpp new file mode 100644 index 0000000000..edffb1a8c3 --- /dev/null +++ b/esphome/components/logger/logger_host.cpp @@ -0,0 +1,24 @@ +#if defined(USE_HOST) +#include "logger.h" + +namespace esphome { +namespace logger { + +void HOT Logger::write_msg_(const char *msg) { + time_t rawtime; + struct tm *timeinfo; + char buffer[80]; + + time(&rawtime); + timeinfo = localtime(&rawtime); + strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo); + fputs(buffer, stdout); + puts(msg); +} + +void Logger::pre_setup() { global_logger = this; } + +} // namespace logger +} // namespace esphome + +#endif diff --git a/esphome/components/logger/logger_libretiny.cpp b/esphome/components/logger/logger_libretiny.cpp new file mode 100644 index 0000000000..12e55b7cef --- /dev/null +++ b/esphome/components/logger/logger_libretiny.cpp @@ -0,0 +1,62 @@ +#ifdef USE_LIBRETINY +#include "logger.h" + +namespace esphome { +namespace logger { + +static const char *const TAG = "logger"; + +void Logger::pre_setup() { + if (this->baud_rate_ > 0) { + switch (this->uart_) { +#if LT_HW_UART0 + case UART_SELECTION_UART0: + this->hw_serial_ = &Serial0; + Serial0.begin(this->baud_rate_); + break; +#endif +#if LT_HW_UART1 + case UART_SELECTION_UART1: + this->hw_serial_ = &Serial1; + Serial1.begin(this->baud_rate_); + break; +#endif +#if LT_HW_UART2 + case UART_SELECTION_UART2: + this->hw_serial_ = &Serial2; + Serial2.begin(this->baud_rate_); + break; +#endif + default: + this->hw_serial_ = &Serial; + Serial.begin(this->baud_rate_); + if (this->uart_ != UART_SELECTION_DEFAULT) { + ESP_LOGW(TAG, " The chosen logger UART port is not available on this board." + "The default port was used instead."); + } + break; + } + + // change lt_log() port to match default Serial + if (this->uart_ == UART_SELECTION_DEFAULT) { + this->uart_ = (UARTSelection) (LT_UART_DEFAULT_SERIAL + 1); + lt_log_set_port(LT_UART_DEFAULT_SERIAL); + } else { + lt_log_set_port(this->uart_ - 1); + } + } + + global_logger = this; + ESP_LOGI(TAG, "Log initialized"); +} + +void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } + +const char *const UART_SELECTIONS[] = {"DEFAULT", "UART0", "UART1", "UART2"}; + +const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } + +} // namespace logger +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/logger/logger_rp2040.cpp b/esphome/components/logger/logger_rp2040.cpp new file mode 100644 index 0000000000..2783d77ac1 --- /dev/null +++ b/esphome/components/logger/logger_rp2040.cpp @@ -0,0 +1,39 @@ +#ifdef USE_RP2040 +#include "logger.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace logger { + +static const char *const TAG = "logger"; + +void Logger::pre_setup() { + if (this->baud_rate_ > 0) { + switch (this->uart_) { + case UART_SELECTION_UART0: + this->hw_serial_ = &Serial1; + Serial1.begin(this->baud_rate_); + break; + case UART_SELECTION_UART1: + this->hw_serial_ = &Serial2; + Serial2.begin(this->baud_rate_); + break; + case UART_SELECTION_USB_CDC: + this->hw_serial_ = &Serial; + Serial.begin(this->baud_rate_); + break; + } + } + global_logger = this; + ESP_LOGI(TAG, "Log initialized"); +} + +void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } + +const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"}; + +const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } + +} // namespace logger +} // namespace esphome +#endif // USE_RP2040 diff --git a/esphome/components/ltr390/ltr390.cpp b/esphome/components/ltr390/ltr390.cpp index 959af68235..65c08ab614 100644 --- a/esphome/components/ltr390/ltr390.cpp +++ b/esphome/components/ltr390/ltr390.cpp @@ -8,10 +8,23 @@ namespace ltr390 { static const char *const TAG = "ltr390"; +static const uint8_t LTR390_MAIN_CTRL = 0x00; +static const uint8_t LTR390_MEAS_RATE = 0x04; +static const uint8_t LTR390_GAIN = 0x05; +static const uint8_t LTR390_PART_ID = 0x06; +static const uint8_t LTR390_MAIN_STATUS = 0x07; + static const float GAINVALUES[5] = {1.0, 3.0, 6.0, 9.0, 18.0}; static const float RESOLUTIONVALUE[6] = {4.0, 2.0, 1.0, 0.5, 0.25, 0.125}; + +// Request fastest measurement rate - will be slowed by device if conversion rate is slower. +static const float RESOLUTION_SETTING[6] = {0x00, 0x10, 0x20, 0x30, 0x40, 0x50}; static const uint32_t MODEADDRESSES[2] = {0x0D, 0x10}; +static const float SENSITIVITY_MAX = 2300; +static const float INTG_MAX = RESOLUTIONVALUE[0] * 100; +static const int GAIN_MAX = GAINVALUES[4]; + uint32_t little_endian_bytes_to_int(const uint8_t *buffer, uint8_t num_bytes) { uint32_t value = 0; @@ -58,7 +71,7 @@ void LTR390Component::read_als_() { uint32_t als = *val; if (this->light_sensor_ != nullptr) { - float lux = (0.6 * als) / (GAINVALUES[this->gain_] * RESOLUTIONVALUE[this->res_]) * this->wfac_; + float lux = ((0.6 * als) / (GAINVALUES[this->gain_] * RESOLUTIONVALUE[this->res_])) * this->wfac_; this->light_sensor_->publish_state(lux); } @@ -74,7 +87,7 @@ void LTR390Component::read_uvs_() { uint32_t uv = *val; if (this->uvi_sensor_ != nullptr) { - this->uvi_sensor_->publish_state(uv / LTR390_SENSITIVITY * this->wfac_); + this->uvi_sensor_->publish_state((uv / this->sensitivity_) * this->wfac_); } if (this->uv_sensor_ != nullptr) { @@ -132,12 +145,13 @@ void LTR390Component::setup() { // Set gain this->reg(LTR390_GAIN) = gain_; - // Set resolution - uint8_t res = this->reg(LTR390_MEAS_RATE).get(); - // resolution is in bits 5-7 - res &= ~0b01110000; - res |= res << 4; - this->reg(LTR390_MEAS_RATE) = res; + // Set resolution and measurement rate + this->reg(LTR390_MEAS_RATE) = RESOLUTION_SETTING[this->res_]; + + // Set sensitivity by linearly scaling against known value in the datasheet + float gain_scale = GAINVALUES[this->gain_] / GAIN_MAX; + float intg_scale = (RESOLUTIONVALUE[this->res_] * 100) / INTG_MAX; + this->sensitivity_ = SENSITIVITY_MAX * gain_scale * intg_scale; // Set sensor read state this->reading_ = false; diff --git a/esphome/components/ltr390/ltr390.h b/esphome/components/ltr390/ltr390.h index 1bb7a8fa22..bc98518fe9 100644 --- a/esphome/components/ltr390/ltr390.h +++ b/esphome/components/ltr390/ltr390.h @@ -17,14 +17,6 @@ enum LTR390CTRL { }; // enums from https://github.com/adafruit/Adafruit_LTR390/ - -static const uint8_t LTR390_MAIN_CTRL = 0x00; -static const uint8_t LTR390_MEAS_RATE = 0x04; -static const uint8_t LTR390_GAIN = 0x05; -static const uint8_t LTR390_PART_ID = 0x06; -static const uint8_t LTR390_MAIN_STATUS = 0x07; -static const float LTR390_SENSITIVITY = 2300.0; - // Sensing modes enum LTR390MODE { LTR390_MODE_ALS, @@ -81,6 +73,7 @@ class LTR390Component : public PollingComponent, public i2c::I2CDevice { LTR390GAIN gain_; LTR390RESOLUTION res_; + float sensitivity_; float wfac_; sensor::Sensor *light_sensor_{nullptr}; diff --git a/esphome/components/ltr390/sensor.py b/esphome/components/ltr390/sensor.py index 0a765dbe3d..fe8cad00b6 100644 --- a/esphome/components/ltr390/sensor.py +++ b/esphome/components/ltr390/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( CONF_RESOLUTION, UNIT_LUX, ICON_BRIGHTNESS_5, + DEVICE_CLASS_EMPTY, DEVICE_CLASS_ILLUMINANCE, ) @@ -61,22 +62,22 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_COUNTS, icon=ICON_BRIGHTNESS_5, accuracy_decimals=1, - device_class=DEVICE_CLASS_ILLUMINANCE, + device_class=DEVICE_CLASS_EMPTY, ), cv.Optional(CONF_UV_INDEX): sensor.sensor_schema( unit_of_measurement=UNIT_UVI, icon=ICON_BRIGHTNESS_5, accuracy_decimals=5, - device_class=DEVICE_CLASS_ILLUMINANCE, + device_class=DEVICE_CLASS_EMPTY, ), cv.Optional(CONF_UV): sensor.sensor_schema( unit_of_measurement=UNIT_COUNTS, icon=ICON_BRIGHTNESS_5, accuracy_decimals=1, - device_class=DEVICE_CLASS_ILLUMINANCE, + device_class=DEVICE_CLASS_EMPTY, ), - cv.Optional(CONF_GAIN, default="X3"): cv.enum(GAIN_OPTIONS), - cv.Optional(CONF_RESOLUTION, default=18): cv.enum(RES_OPTIONS), + cv.Optional(CONF_GAIN, default="X18"): cv.enum(GAIN_OPTIONS), + cv.Optional(CONF_RESOLUTION, default=20): cv.enum(RES_OPTIONS), cv.Optional(CONF_WINDOW_CORRECTION_FACTOR, default=1.0): cv.float_range( min=1.0 ), diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index 9efefd58cc..82cf087fdc 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -88,7 +88,7 @@ async def to_code(config): add_idf_component( name="mdns", repo="https://github.com/espressif/esp-protocols.git", - ref="mdns-v1.2.2", + ref="mdns-v1.2.5", path="components/mdns", ) diff --git a/esphome/components/media_player/__init__.py b/esphome/components/media_player/__init__.py index 80f5fc558a..86e038d76d 100644 --- a/esphome/components/media_player/__init__.py +++ b/esphome/components/media_player/__init__.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv import esphome.codegen as cg from esphome.automation import maybe_simple_id -from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID +from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID, CONF_VOLUME from esphome.core import CORE from esphome.coroutine import coroutine_with_priority from esphome.cpp_helpers import setup_entity @@ -43,7 +43,6 @@ VolumeSetAction = media_player_ns.class_( ) -CONF_VOLUME = "volume" CONF_ON_IDLE = "on_idle" CONF_ON_PLAY = "on_play" CONF_ON_PAUSE = "on_pause" diff --git a/esphome/components/mhz19/mhz19.cpp b/esphome/components/mhz19/mhz19.cpp index db3ad50851..019f6cee51 100644 --- a/esphome/components/mhz19/mhz19.cpp +++ b/esphome/components/mhz19/mhz19.cpp @@ -29,6 +29,14 @@ void MHZ19Component::setup() { } void MHZ19Component::update() { + uint32_t now_ms = millis(); + uint32_t warmup_ms = this->warmup_seconds_ * 1000; + if (now_ms < warmup_ms) { + ESP_LOGW(TAG, "MHZ19 warming up, %ds left", (warmup_ms - now_ms) / 1000); + this->status_set_warning(); + return; + } + uint8_t response[MHZ19_RESPONSE_LENGTH]; if (!this->mhz19_write_command_(MHZ19_COMMAND_GET_PPM, response)) { ESP_LOGW(TAG, "Reading data from MHZ19 failed!"); @@ -101,6 +109,8 @@ void MHZ19Component::dump_config() { } else if (this->abc_boot_logic_ == MHZ19_ABC_DISABLED) { ESP_LOGCONFIG(TAG, " Automatic baseline calibration disabled on boot"); } + + ESP_LOGCONFIG(TAG, " Warmup seconds: %ds", this->warmup_seconds_); } } // namespace mhz19 diff --git a/esphome/components/mhz19/mhz19.h b/esphome/components/mhz19/mhz19.h index 151351be4c..ec38f2cd2f 100644 --- a/esphome/components/mhz19/mhz19.h +++ b/esphome/components/mhz19/mhz19.h @@ -25,6 +25,7 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice { void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; } void set_abc_enabled(bool abc_enabled) { abc_boot_logic_ = abc_enabled ? MHZ19_ABC_ENABLED : MHZ19_ABC_DISABLED; } + void set_warmup_seconds(uint32_t seconds) { warmup_seconds_ = seconds; } protected: bool mhz19_write_command_(const uint8_t *command, uint8_t *response); @@ -32,6 +33,7 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice { sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *co2_sensor_{nullptr}; MHZ19ABCLogic abc_boot_logic_{MHZ19_ABC_NONE}; + uint32_t warmup_seconds_; }; template class MHZ19CalibrateZeroAction : public Action { diff --git a/esphome/components/mhz19/sensor.py b/esphome/components/mhz19/sensor.py index 0081f42952..3956727981 100644 --- a/esphome/components/mhz19/sensor.py +++ b/esphome/components/mhz19/sensor.py @@ -18,6 +18,7 @@ from esphome.const import ( DEPENDENCIES = ["uart"] CONF_AUTOMATIC_BASELINE_CALIBRATION = "automatic_baseline_calibration" +CONF_WARMUP_TIME = "warmup_time" mhz19_ns = cg.esphome_ns.namespace("mhz19") MHZ19Component = mhz19_ns.class_("MHZ19Component", cg.PollingComponent, uart.UARTDevice) @@ -45,6 +46,9 @@ CONFIG_SCHEMA = ( state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_AUTOMATIC_BASELINE_CALIBRATION): cv.boolean, + cv.Optional( + CONF_WARMUP_TIME, default="75s" + ): cv.positive_time_period_seconds, } ) .extend(cv.polling_component_schema("60s")) @@ -68,6 +72,8 @@ async def to_code(config): if CONF_AUTOMATIC_BASELINE_CALIBRATION in config: cg.add(var.set_abc_enabled(config[CONF_AUTOMATIC_BASELINE_CALIBRATION])) + cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME])) + CALIBRATION_ACTION_SCHEMA = maybe_simple_id( { diff --git a/esphome/components/micro_wake_word/__init__.py b/esphome/components/micro_wake_word/__init__.py new file mode 100644 index 0000000000..9073d103f1 --- /dev/null +++ b/esphome/components/micro_wake_word/__init__.py @@ -0,0 +1,367 @@ +import logging + +import json +import hashlib +from urllib.parse import urljoin +from pathlib import Path +import requests + +import esphome.config_validation as cv +import esphome.codegen as cg + +from esphome.core import CORE, HexInt, EsphomeError + +from esphome.components import esp32, microphone +from esphome import automation, git, external_files +from esphome.automation import register_action, register_condition + + +from esphome.const import ( + __version__, + CONF_ID, + CONF_MICROPHONE, + CONF_MODEL, + CONF_URL, + CONF_FILE, + CONF_PATH, + CONF_REF, + CONF_REFRESH, + CONF_TYPE, + CONF_USERNAME, + CONF_PASSWORD, + CONF_RAW_DATA_ID, + TYPE_GIT, + TYPE_LOCAL, +) + + +_LOGGER = logging.getLogger(__name__) + +CODEOWNERS = ["@kahrendt", "@jesserockz"] +DEPENDENCIES = ["microphone"] +DOMAIN = "micro_wake_word" + +CONF_PROBABILITY_CUTOFF = "probability_cutoff" +CONF_SLIDING_WINDOW_AVERAGE_SIZE = "sliding_window_average_size" +CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected" + +TYPE_HTTP = "http" + +micro_wake_word_ns = cg.esphome_ns.namespace("micro_wake_word") + +MicroWakeWord = micro_wake_word_ns.class_("MicroWakeWord", cg.Component) + +StartAction = micro_wake_word_ns.class_("StartAction", automation.Action) +StopAction = micro_wake_word_ns.class_("StopAction", automation.Action) + +IsRunningCondition = micro_wake_word_ns.class_( + "IsRunningCondition", automation.Condition +) + + +def _validate_json_filename(value): + value = cv.string(value) + if not value.endswith(".json"): + raise cv.Invalid("Manifest filename must end with .json") + return value + + +def _process_git_source(config): + repo_dir, _ = git.clone_or_update( + url=config[CONF_URL], + ref=config.get(CONF_REF), + refresh=config[CONF_REFRESH], + domain=DOMAIN, + username=config.get(CONF_USERNAME), + password=config.get(CONF_PASSWORD), + ) + + if not (repo_dir / config[CONF_FILE]).exists(): + raise cv.Invalid("File does not exist in repository") + + return config + + +CV_GIT_SCHEMA = cv.GIT_SCHEMA +if isinstance(CV_GIT_SCHEMA, dict): + CV_GIT_SCHEMA = cv.Schema(CV_GIT_SCHEMA) + +GIT_SCHEMA = cv.All( + CV_GIT_SCHEMA.extend( + { + cv.Required(CONF_FILE): _validate_json_filename, + cv.Optional(CONF_REFRESH, default="1d"): cv.All( + cv.string, cv.source_refresh + ), + } + ), + _process_git_source, +) + +KEY_WAKE_WORD = "wake_word" +KEY_AUTHOR = "author" +KEY_WEBSITE = "website" +KEY_VERSION = "version" +KEY_MICRO = "micro" +KEY_MINIMUM_ESPHOME_VERSION = "minimum_esphome_version" + +MANIFEST_SCHEMA_V1 = cv.Schema( + { + cv.Required(CONF_TYPE): "micro", + cv.Required(KEY_WAKE_WORD): cv.string, + cv.Required(KEY_AUTHOR): cv.string, + cv.Required(KEY_WEBSITE): cv.url, + cv.Required(KEY_VERSION): cv.All(cv.int_, 1), + cv.Required(CONF_MODEL): cv.string, + cv.Required(KEY_MICRO): cv.Schema( + { + cv.Required(CONF_PROBABILITY_CUTOFF): cv.float_, + cv.Required(CONF_SLIDING_WINDOW_AVERAGE_SIZE): cv.positive_int, + cv.Optional(KEY_MINIMUM_ESPHOME_VERSION): cv.All( + cv.version_number, cv.validate_esphome_version + ), + } + ), + } +) + + +def _compute_local_file_path(config: dict) -> Path: + url = config[CONF_URL] + h = hashlib.new("sha256") + h.update(url.encode()) + key = h.hexdigest()[:8] + base_dir = external_files.compute_local_file_dir(DOMAIN) + return base_dir / key + + +def _download_file(url: str, path: Path) -> bytes: + if not external_files.has_remote_file_changed(url, path): + _LOGGER.debug("Remote file has not changed, skipping download") + return path.read_bytes() + + try: + req = requests.get( + url, + timeout=external_files.NETWORK_TIMEOUT, + headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"}, + ) + req.raise_for_status() + except requests.exceptions.RequestException as e: + raise cv.Invalid(f"Could not download file from {url}: {e}") from e + + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(req.content) + return req.content + + +def _process_http_source(config): + url = config[CONF_URL] + path = _compute_local_file_path(config) + + json_path = path / "manifest.json" + + json_contents = _download_file(url, json_path) + + manifest_data = json.loads(json_contents) + if not isinstance(manifest_data, dict): + raise cv.Invalid("Manifest file must contain a JSON object") + + try: + MANIFEST_SCHEMA_V1(manifest_data) + except cv.Invalid as e: + raise cv.Invalid(f"Invalid manifest file: {e}") from e + + model = manifest_data[CONF_MODEL] + model_url = urljoin(url, model) + + model_path = path / model + + _download_file(str(model_url), model_path) + + return config + + +HTTP_SCHEMA = cv.All( + { + cv.Required(CONF_URL): cv.url, + }, + _process_http_source, +) + +LOCAL_SCHEMA = cv.Schema( + { + cv.Required(CONF_PATH): cv.All(_validate_json_filename, cv.file_), + } +) + + +def _validate_source_model_name(value): + if not isinstance(value, str): + raise cv.Invalid("Model name must be a string") + + if value.endswith(".json"): + raise cv.Invalid("Model name must not end with .json") + + return MODEL_SOURCE_SCHEMA( + { + CONF_TYPE: TYPE_HTTP, + CONF_URL: f"https://github.com/esphome/micro-wake-word-models/raw/main/models/{value}.json", + } + ) + + +def _validate_source_shorthand(value): + if not isinstance(value, str): + raise cv.Invalid("Shorthand only for strings") + + try: # Test for model name + return _validate_source_model_name(value) + except cv.Invalid: + pass + + try: # Test for local path + return MODEL_SOURCE_SCHEMA({CONF_TYPE: TYPE_LOCAL, CONF_PATH: value}) + except cv.Invalid: + pass + + try: # Test for http url + return MODEL_SOURCE_SCHEMA({CONF_TYPE: TYPE_HTTP, CONF_URL: value}) + except cv.Invalid: + pass + + git_file = git.GitFile.from_shorthand(value) + + conf = { + CONF_TYPE: TYPE_GIT, + CONF_URL: git_file.git_url, + CONF_FILE: git_file.filename, + } + if git_file.ref: + conf[CONF_REF] = git_file.ref + + try: + return MODEL_SOURCE_SCHEMA(conf) + except cv.Invalid as e: + raise cv.Invalid( + f"Could not find file '{git_file.filename}' in the repository. Please make sure it exists." + ) from e + + +MODEL_SOURCE_SCHEMA = cv.Any( + _validate_source_shorthand, + cv.typed_schema( + { + TYPE_GIT: GIT_SCHEMA, + TYPE_LOCAL: LOCAL_SCHEMA, + TYPE_HTTP: HTTP_SCHEMA, + } + ), + msg="Not a valid model name, local path, http(s) url, or github shorthand", +) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MicroWakeWord), + cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone), + cv.Optional(CONF_PROBABILITY_CUTOFF): cv.percentage, + cv.Optional(CONF_SLIDING_WINDOW_AVERAGE_SIZE): cv.positive_int, + cv.Optional(CONF_ON_WAKE_WORD_DETECTED): automation.validate_automation( + single=True + ), + cv.Required(CONF_MODEL): MODEL_SOURCE_SCHEMA, + cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.only_with_esp_idf, +) + + +def _load_model_data(manifest_path: Path): + with open(manifest_path, encoding="utf-8") as f: + manifest = json.load(f) + + try: + MANIFEST_SCHEMA_V1(manifest) + except cv.Invalid as e: + raise EsphomeError(f"Invalid manifest file: {e}") from e + + model_path = manifest_path.parent / manifest[CONF_MODEL] + + with open(model_path, "rb") as f: + model = f.read() + + return manifest, model + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + mic = await cg.get_variable(config[CONF_MICROPHONE]) + cg.add(var.set_microphone(mic)) + + if on_wake_word_detection_config := config.get(CONF_ON_WAKE_WORD_DETECTED): + await automation.build_automation( + var.get_wake_word_detected_trigger(), + [(cg.std_string, "wake_word")], + on_wake_word_detection_config, + ) + + esp32.add_idf_component( + name="esp-tflite-micro", + repo="https://github.com/espressif/esp-tflite-micro", + ) + + cg.add_build_flag("-DTF_LITE_STATIC_MEMORY") + cg.add_build_flag("-DTF_LITE_DISABLE_X86_NEON") + cg.add_build_flag("-DESP_NN") + + model_config = config.get(CONF_MODEL) + data = [] + if model_config[CONF_TYPE] == TYPE_GIT: + # compute path to model file + key = f"{model_config[CONF_URL]}@{model_config.get(CONF_REF)}" + base_dir = Path(CORE.data_dir) / DOMAIN + h = hashlib.new("sha256") + h.update(key.encode()) + file: Path = base_dir / h.hexdigest()[:8] / model_config[CONF_FILE] + + elif model_config[CONF_TYPE] == TYPE_LOCAL: + file = model_config[CONF_PATH] + + elif model_config[CONF_TYPE] == TYPE_HTTP: + file = _compute_local_file_path(model_config) / "manifest.json" + + manifest, data = _load_model_data(file) + + rhs = [HexInt(x) for x in data] + prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) + cg.add(var.set_model_start(prog_arr)) + + probability_cutoff = config.get( + CONF_PROBABILITY_CUTOFF, manifest[KEY_MICRO][CONF_PROBABILITY_CUTOFF] + ) + cg.add(var.set_probability_cutoff(probability_cutoff)) + sliding_window_average_size = config.get( + CONF_SLIDING_WINDOW_AVERAGE_SIZE, + manifest[KEY_MICRO][CONF_SLIDING_WINDOW_AVERAGE_SIZE], + ) + cg.add(var.set_sliding_window_average_size(sliding_window_average_size)) + + cg.add(var.set_wake_word(manifest[KEY_WAKE_WORD])) + + +MICRO_WAKE_WORD_ACTION_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(MicroWakeWord)}) + + +@register_action("micro_wake_word.start", StartAction, MICRO_WAKE_WORD_ACTION_SCHEMA) +@register_action("micro_wake_word.stop", StopAction, MICRO_WAKE_WORD_ACTION_SCHEMA) +@register_condition( + "micro_wake_word.is_running", IsRunningCondition, MICRO_WAKE_WORD_ACTION_SCHEMA +) +async def micro_wake_word_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var diff --git a/esphome/components/micro_wake_word/audio_preprocessor_int8_model_data.h b/esphome/components/micro_wake_word/audio_preprocessor_int8_model_data.h new file mode 100644 index 0000000000..918e76045f --- /dev/null +++ b/esphome/components/micro_wake_word/audio_preprocessor_int8_model_data.h @@ -0,0 +1,493 @@ +#pragma once + +#ifdef USE_ESP_IDF + +// Converted audio_preprocessor_int8.tflite +// From https://github.com/tensorflow/tflite-micro/tree/main/tensorflow/lite/micro/examples/micro_speech/models accessed +// January 2024 +// +// Copyright 2023 The TensorFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace esphome { +namespace micro_wake_word { + +const unsigned char G_AUDIO_PREPROCESSOR_INT8_TFLITE[] = { + 0x1c, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x14, 0x00, 0x20, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, + 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x88, 0x00, + 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x80, 0x0e, 0x00, 0x00, 0x90, 0x0e, 0x00, 0x00, 0xcc, 0x1f, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xe2, 0xeb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, + 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x94, 0xff, + 0xff, 0xff, 0x2a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x5f, 0x30, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc2, 0xf5, 0xff, 0xff, + 0x04, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xdc, 0xff, 0xff, 0xff, 0x2d, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, + 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x6d, 0x69, 0x6e, + 0x5f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x2e, 0x00, + 0x00, 0x00, 0x9c, 0x0d, 0x00, 0x00, 0x94, 0x0d, 0x00, 0x00, 0xc4, 0x09, 0x00, 0x00, 0x6c, 0x09, 0x00, 0x00, 0x48, + 0x09, 0x00, 0x00, 0x34, 0x09, 0x00, 0x00, 0x20, 0x09, 0x00, 0x00, 0x0c, 0x09, 0x00, 0x00, 0xf8, 0x08, 0x00, 0x00, + 0xec, 0x07, 0x00, 0x00, 0x88, 0x07, 0x00, 0x00, 0x24, 0x07, 0x00, 0x00, 0xc0, 0x06, 0x00, 0x00, 0x38, 0x04, 0x00, + 0x00, 0xb0, 0x01, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x88, 0x01, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x60, 0x01, + 0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x34, 0x01, 0x00, 0x00, 0x2c, + 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x00, 0x1c, 0x01, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, + 0x04, 0x01, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xec, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, + 0x00, 0xdc, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0xbc, 0x00, + 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x94, + 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf2, 0xf6, 0xff, 0xff, + 0x04, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, + 0x07, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x32, 0x2e, 0x31, 0x32, 0x2e, 0x30, 0x00, + 0x00, 0x56, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x32, 0x2e, 0x38, 0x2e, 0x30, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0xe1, 0xff, 0xff, 0xd8, 0xe1, 0xff, 0xff, 0xdc, + 0xe1, 0xff, 0xff, 0xe0, 0xe1, 0xff, 0xff, 0xe4, 0xe1, 0xff, 0xff, 0xe8, 0xe1, 0xff, 0xff, 0xec, 0xe1, 0xff, 0xff, + 0xf0, 0xe1, 0xff, 0xff, 0xf4, 0xe1, 0xff, 0xff, 0xf8, 0xe1, 0xff, 0xff, 0xfc, 0xe1, 0xff, 0xff, 0x00, 0xe2, 0xff, + 0xff, 0x04, 0xe2, 0xff, 0xff, 0x08, 0xe2, 0xff, 0xff, 0x0c, 0xe2, 0xff, 0xff, 0x10, 0xe2, 0xff, 0xff, 0x14, 0xe2, + 0xff, 0xff, 0x18, 0xe2, 0xff, 0xff, 0x1c, 0xe2, 0xff, 0xff, 0x20, 0xe2, 0xff, 0xff, 0x24, 0xe2, 0xff, 0xff, 0x28, + 0xe2, 0xff, 0xff, 0x2c, 0xe2, 0xff, 0xff, 0x30, 0xe2, 0xff, 0xff, 0xd2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0xe2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0xf2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x80, 0xff, + 0xff, 0xff, 0x02, 0xf8, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, + 0xf8, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x22, 0xf8, 0xff, 0xff, + 0x04, 0x00, 0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x61, 0x05, 0x00, 0x00, 0x00, 0x00, 0x23, 0x0b, 0x41, + 0x01, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x0e, 0x80, 0x05, + 0x00, 0x00, 0x00, 0x00, 0xd1, 0x0c, 0x63, 0x04, 0x00, 0x00, 0x00, 0x00, 0x34, 0x0c, 0x3f, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x81, 0x0c, 0xf7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x0d, 0x77, 0x06, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x0f, + 0xa9, 0x08, 0x01, 0x02, 0x7f, 0x0b, 0x22, 0x05, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x0e, 0xd1, 0x08, 0xdb, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x0d, 0x4a, 0x07, 0xad, 0x01, 0x2c, 0x0c, 0xc6, 0x06, 0x79, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x45, 0x0c, 0x29, 0x07, 0x23, 0x02, 0x34, 0x0d, 0x5b, 0x08, 0x96, 0x03, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x0e, 0x48, + 0x0a, 0xbd, 0x05, 0x45, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x0c, 0x88, 0x08, 0x43, 0x04, + 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x0b, 0xd3, 0x07, 0xcb, 0x03, 0xd2, 0x0f, 0xe7, + 0x0b, 0x09, 0x08, 0x39, 0x04, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x0c, 0x14, 0x09, + 0x75, 0x05, 0xe2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x0e, 0xdd, 0x0a, 0x6b, 0x07, 0x03, + 0x04, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x0d, 0x09, 0x0a, 0xc9, 0x06, 0x93, 0x03, 0x65, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x0d, 0x25, 0x0a, 0x12, 0x07, 0x07, 0x04, 0x05, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x0a, 0x0e, 0x17, 0x0b, 0x2c, 0x08, 0x49, 0x05, 0x6d, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x98, 0x0f, 0xcb, 0x0c, 0x04, 0x0a, 0x44, 0x07, 0x8b, 0x04, 0xd8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x0f, 0x87, + 0x0c, 0xe7, 0x09, 0x4e, 0x07, 0xba, 0x04, 0x2d, 0x02, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x0f, 0x23, 0x0d, 0xa7, 0x0a, + 0x30, 0x08, 0xbe, 0x05, 0x52, 0x03, 0xeb, 0x00, 0x89, 0x0e, 0x2c, 0x0c, 0xd4, 0x09, 0x81, 0x07, 0x33, 0x05, 0xe9, + 0x02, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x0e, 0x29, 0x0c, 0xf1, 0x09, 0xbe, 0x07, 0x90, 0x05, 0x65, 0x03, + 0x3f, 0x01, 0x1d, 0x0f, 0xff, 0x0c, 0xe5, 0x0a, 0xcf, 0x08, 0xbc, 0x06, 0xae, 0x04, 0xa3, 0x02, 0x9c, 0x00, 0x99, + 0x0e, 0x99, 0x0c, 0x9d, 0x0a, 0xa4, 0x08, 0xaf, 0x06, 0xbd, 0x04, 0xcf, 0x02, 0xe4, 0x00, 0xfc, 0x0e, 0x17, 0x0d, + 0x36, 0x0b, 0x57, 0x09, 0x7c, 0x07, 0xa4, 0x05, 0xcf, 0x03, 0xfd, 0x01, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x62, 0x0e, 0x98, 0x0c, 0xd2, 0x0a, 0x0e, 0x09, 0x4d, 0x07, 0x8f, 0x05, 0xd4, 0x03, 0x1b, 0x02, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x0e, 0x00, 0x0d, 0x52, 0x0b, 0xa6, 0x09, 0xfd, 0x07, 0x56, 0x06, 0xb1, + 0x04, 0x0f, 0x03, 0x6f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0x0f, 0x37, 0x0e, 0x9e, 0x0c, + 0x08, 0x0b, 0x73, 0x09, 0xe1, 0x07, 0x52, 0x06, 0xc4, 0x04, 0x38, 0x03, 0xaf, 0x01, 0x28, 0x00, 0xa3, 0x0e, 0x1f, + 0x0d, 0x9e, 0x0b, 0x1f, 0x0a, 0xa2, 0x08, 0x27, 0x07, 0xae, 0x05, 0x37, 0x04, 0xc2, 0x02, 0x4e, 0x01, 0x00, 0x00, + 0x00, 0x00, 0xdd, 0x0f, 0x6d, 0x0e, 0xff, 0x0c, 0x93, 0x0b, 0x29, 0x0a, 0xc1, 0x08, 0x5a, 0x07, 0xf5, 0x05, 0x92, + 0x04, 0x30, 0x03, 0xd1, 0x01, 0x73, 0x00, 0x16, 0x0f, 0xbc, 0x0d, 0x62, 0x0c, 0x0b, 0x0b, 0xb5, 0x09, 0x61, 0x08, + 0x0e, 0x07, 0xbd, 0x05, 0x6d, 0x04, 0x1f, 0x03, 0xd3, 0x01, 0x88, 0x00, 0x3e, 0x0f, 0xf6, 0x0d, 0xaf, 0x0c, 0x6a, + 0x0b, 0x27, 0x0a, 0xe4, 0x08, 0xa3, 0x07, 0x64, 0x06, 0x26, 0x05, 0xe9, 0x03, 0xae, 0x02, 0x74, 0x01, 0x3b, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0f, 0xce, 0x0d, 0x99, 0x0c, 0x66, 0x0b, 0x34, 0x0a, 0x03, + 0x09, 0xd3, 0x07, 0xa5, 0x06, 0x78, 0x05, 0x4c, 0x04, 0x22, 0x03, 0xf8, 0x01, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xa9, 0x0f, 0x83, 0x0e, 0x5f, 0x0d, 0x3b, 0x0c, 0x19, 0x0b, 0xf8, 0x09, 0xd8, 0x08, 0xb9, 0x07, 0x9b, 0x06, 0x7e, + 0x05, 0x63, 0x04, 0x48, 0x03, 0x2f, 0x02, 0x17, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa6, 0xfa, 0xff, 0xff, 0x04, 0x00, + 0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x04, 0xbe, 0x0e, 0x00, + 0x00, 0x00, 0x00, 0x4c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x01, 0x7f, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0x2e, 0x03, 0x9c, 0x0b, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x03, 0xc0, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x7e, + 0x03, 0x08, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x60, 0x02, 0x88, 0x09, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x56, 0x07, + 0xfe, 0x0d, 0x80, 0x04, 0xdd, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x2e, 0x07, 0x24, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0xfc, 0x02, 0xb5, 0x08, 0x52, 0x0e, 0xd3, 0x03, 0x39, 0x09, 0x86, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xba, 0x03, + 0xd6, 0x08, 0xdc, 0x0d, 0xcb, 0x02, 0xa4, 0x07, 0x69, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0xb7, 0x05, 0x42, + 0x0a, 0xba, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x03, 0x77, 0x07, 0xbc, 0x0b, 0xf1, 0x0f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x04, 0x2c, 0x08, 0x34, 0x0c, 0x2d, 0x00, 0x18, 0x04, 0xf6, + 0x07, 0xc6, 0x0b, 0x89, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0xeb, 0x06, 0x8a, 0x0a, + 0x1d, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x01, 0x22, 0x05, 0x94, 0x08, 0xfc, 0x0b, 0x59, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0xac, 0x02, 0xf6, 0x05, 0x36, 0x09, 0x6c, 0x0c, 0x9a, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbe, 0x02, 0xda, 0x05, 0xed, 0x08, 0xf8, 0x0b, 0xfa, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xf5, + 0x01, 0xe8, 0x04, 0xd3, 0x07, 0xb6, 0x0a, 0x92, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, + 0x34, 0x03, 0xfb, 0x05, 0xbb, 0x08, 0x74, 0x0b, 0x27, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x00, 0x78, 0x03, 0x18, + 0x06, 0xb1, 0x08, 0x45, 0x0b, 0xd2, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x00, 0xdc, 0x02, 0x58, 0x05, 0xcf, 0x07, + 0x41, 0x0a, 0xad, 0x0c, 0x14, 0x0f, 0x76, 0x01, 0xd3, 0x03, 0x2b, 0x06, 0x7e, 0x08, 0xcc, 0x0a, 0x16, 0x0d, 0x5a, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x01, 0xd6, 0x03, 0x0e, 0x06, 0x41, 0x08, 0x6f, 0x0a, 0x9a, 0x0c, 0xc0, 0x0e, + 0xe2, 0x00, 0x00, 0x03, 0x1a, 0x05, 0x30, 0x07, 0x43, 0x09, 0x51, 0x0b, 0x5c, 0x0d, 0x63, 0x0f, 0x66, 0x01, 0x66, + 0x03, 0x62, 0x05, 0x5b, 0x07, 0x50, 0x09, 0x42, 0x0b, 0x30, 0x0d, 0x1b, 0x0f, 0x03, 0x01, 0xe8, 0x02, 0xc9, 0x04, + 0xa8, 0x06, 0x83, 0x08, 0x5b, 0x0a, 0x30, 0x0c, 0x02, 0x0e, 0xd1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x9d, 0x01, 0x67, 0x03, 0x2d, 0x05, 0xf1, 0x06, 0xb2, 0x08, 0x70, 0x0a, 0x2b, 0x0c, 0xe4, 0x0d, 0x9a, 0x0f, + 0x00, 0x00, 0x00, 0x00, 0x4e, 0x01, 0xff, 0x02, 0xad, 0x04, 0x59, 0x06, 0x02, 0x08, 0xa9, 0x09, 0x4e, 0x0b, 0xf0, + 0x0c, 0x90, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0xc8, 0x01, 0x61, 0x03, 0xf7, 0x04, + 0x8c, 0x06, 0x1e, 0x08, 0xad, 0x09, 0x3b, 0x0b, 0xc7, 0x0c, 0x50, 0x0e, 0xd7, 0x0f, 0x5c, 0x01, 0xe0, 0x02, 0x61, + 0x04, 0xe0, 0x05, 0x5d, 0x07, 0xd8, 0x08, 0x51, 0x0a, 0xc8, 0x0b, 0x3d, 0x0d, 0xb1, 0x0e, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x92, 0x01, 0x00, 0x03, 0x6c, 0x04, 0xd6, 0x05, 0x3e, 0x07, 0xa5, 0x08, 0x0a, 0x0a, 0x6d, 0x0b, 0xcf, + 0x0c, 0x2e, 0x0e, 0x8c, 0x0f, 0xe9, 0x00, 0x43, 0x02, 0x9d, 0x03, 0xf4, 0x04, 0x4a, 0x06, 0x9e, 0x07, 0xf1, 0x08, + 0x42, 0x0a, 0x92, 0x0b, 0xe0, 0x0c, 0x2c, 0x0e, 0x77, 0x0f, 0xc1, 0x00, 0x09, 0x02, 0x50, 0x03, 0x95, 0x04, 0xd8, + 0x05, 0x1b, 0x07, 0x5c, 0x08, 0x9b, 0x09, 0xd9, 0x0a, 0x16, 0x0c, 0x51, 0x0d, 0x8b, 0x0e, 0xc4, 0x0f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0x00, 0x31, 0x02, 0x66, 0x03, 0x99, 0x04, 0xcb, 0x05, 0xfc, 0x06, 0x2c, + 0x08, 0x5a, 0x09, 0x87, 0x0a, 0xb3, 0x0b, 0xdd, 0x0c, 0x07, 0x0e, 0x2f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x56, 0x00, + 0x7c, 0x01, 0xa0, 0x02, 0xc4, 0x03, 0xe6, 0x04, 0x07, 0x06, 0x27, 0x07, 0x46, 0x08, 0x64, 0x09, 0x81, 0x0a, 0x9c, + 0x0b, 0xb7, 0x0c, 0xd0, 0x0d, 0xe8, 0x0e, 0x00, 0x10, 0x00, 0x00, 0x2a, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0e, 0x00, 0x10, + 0x00, 0x12, 0x00, 0x16, 0x00, 0x18, 0x00, 0x1a, 0x00, 0x1e, 0x00, 0x20, 0x00, 0x24, 0x00, 0x26, 0x00, 0x2a, 0x00, + 0x2e, 0x00, 0x32, 0x00, 0x36, 0x00, 0x3a, 0x00, 0x40, 0x00, 0x44, 0x00, 0x4a, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x5a, + 0x00, 0x62, 0x00, 0x68, 0x00, 0x70, 0x00, 0x78, 0x00, 0x80, 0x00, 0x88, 0x00, 0x92, 0x00, 0x9a, 0x00, 0xa6, 0x00, + 0xb0, 0x00, 0xbc, 0x00, 0xc8, 0x00, 0xd4, 0x00, 0xe2, 0x00, 0x00, 0x00, 0x8a, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00, + 0x00, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00, 0x18, 0x00, + 0x1c, 0x00, 0x20, 0x00, 0x24, 0x00, 0x28, 0x00, 0x2c, 0x00, 0x30, 0x00, 0x34, 0x00, 0x38, 0x00, 0x3c, 0x00, 0x44, + 0x00, 0x4c, 0x00, 0x50, 0x00, 0x58, 0x00, 0x60, 0x00, 0x68, 0x00, 0x70, 0x00, 0x78, 0x00, 0x80, 0x00, 0x88, 0x00, + 0x90, 0x00, 0x98, 0x00, 0xa0, 0x00, 0xa8, 0x00, 0xb0, 0x00, 0xb8, 0x00, 0xc4, 0x00, 0xd0, 0x00, 0xdc, 0x00, 0xe8, + 0x00, 0xf4, 0x00, 0x00, 0x01, 0x0c, 0x01, 0x1c, 0x01, 0x2c, 0x01, 0x00, 0x00, 0xea, 0xfd, 0xff, 0xff, 0x04, 0x00, + 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, + 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, + 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, + 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4a, 0xfe, 0xff, 0xff, 0x04, + 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x7c, 0x7f, 0x79, 0x7f, 0x76, 0x7f, 0xfa, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x70, 0x7f, 0xf4, 0xff, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7f, 0xe9, 0xff, 0xfe, 0xff, 0x00, 0x00, 0x4b, 0x7f, 0xd0, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x7f, 0xa0, 0xff, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x7e, 0x42, 0xff, 0x00, 0x00, + 0x00, 0x00, 0xfd, 0x7d, 0x86, 0xfe, 0x04, 0x00, 0x00, 0x00, 0x87, 0x7c, 0x1d, 0xfd, 0x12, 0x00, 0x00, 0x00, 0xb6, + 0x79, 0x7f, 0xfa, 0x3e, 0x00, 0x00, 0x00, 0x73, 0x74, 0xf9, 0xf5, 0xca, 0x00, 0x00, 0x00, 0x36, 0x6b, 0x33, 0xef, + 0x32, 0x02, 0x00, 0x00, 0x9b, 0x5c, 0x87, 0xe7, 0xce, 0x04, 0x00, 0x00, 0xf0, 0x48, 0xde, 0xe2, 0xa0, 0x07, 0x00, + 0x00, 0x6e, 0x33, 0x8a, 0xe4, 0xa4, 0x08, 0x00, 0x00, 0x9c, 0x20, 0x22, 0xeb, 0x4c, 0x07, 0x00, 0x00, 0x0a, 0x13, + 0x7d, 0xf2, 0x02, 0x05, 0x00, 0x00, 0x89, 0x0a, 0x17, 0xf8, 0x06, 0x03, 0x00, 0x00, 0xa6, 0x05, 0xa0, 0xfb, 0xb4, + 0x01, 0x00, 0x00, 0xfa, 0x02, 0xac, 0xfd, 0xe8, 0x00, 0x00, 0x00, 0x8e, 0x01, 0xc7, 0xfe, 0x7a, 0x00, 0x00, 0x00, + 0xcf, 0x00, 0x5c, 0xff, 0x40, 0x00, 0x00, 0x00, 0x6b, 0x00, 0xab, 0xff, 0x22, 0x00, 0x00, 0x00, 0x38, 0x00, 0xd3, + 0xff, 0x12, 0x00, 0x00, 0x00, 0x1d, 0x00, 0xea, 0xff, 0x08, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xf3, 0xff, 0x06, 0x00, + 0x00, 0x00, 0x08, 0x00, 0xf8, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0xfd, 0xff, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xfd, 0xff, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x62, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0xf1, 0x00, 0x00, 0x00, 0x72, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x82, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x4d, 0x01, 0x00, 0x00, + 0x92, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb2, 0xff, 0xff, 0xff, 0x04, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0a, 0x00, 0x0d, 0x00, 0x10, 0x00, 0x13, 0x00, 0x17, 0x00, + 0x1b, 0x00, 0x20, 0x00, 0x25, 0x00, 0x2a, 0x00, 0x30, 0x00, 0x35, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x49, 0x00, 0x51, + 0x00, 0x58, 0x00, 0x60, 0x00, 0x68, 0x00, 0x71, 0x00, 0x7a, 0x00, 0x83, 0x00, 0x8d, 0x00, 0x97, 0x00, 0xa1, 0x00, + 0xac, 0x00, 0xb7, 0x00, 0xc2, 0x00, 0xcd, 0x00, 0xd9, 0x00, 0xe5, 0x00, 0xf2, 0x00, 0xff, 0x00, 0x0c, 0x01, 0x19, + 0x01, 0x27, 0x01, 0x35, 0x01, 0x43, 0x01, 0x52, 0x01, 0x61, 0x01, 0x70, 0x01, 0x7f, 0x01, 0x8f, 0x01, 0x9f, 0x01, + 0xaf, 0x01, 0xc0, 0x01, 0xd1, 0x01, 0xe2, 0x01, 0xf3, 0x01, 0x05, 0x02, 0x17, 0x02, 0x29, 0x02, 0x3c, 0x02, 0x4e, + 0x02, 0x61, 0x02, 0x75, 0x02, 0x88, 0x02, 0x9c, 0x02, 0xb0, 0x02, 0xc4, 0x02, 0xd8, 0x02, 0xed, 0x02, 0x02, 0x03, + 0x17, 0x03, 0x2c, 0x03, 0x41, 0x03, 0x57, 0x03, 0x6d, 0x03, 0x83, 0x03, 0x99, 0x03, 0xb0, 0x03, 0xc7, 0x03, 0xdd, + 0x03, 0xf4, 0x03, 0x0c, 0x04, 0x23, 0x04, 0x3b, 0x04, 0x52, 0x04, 0x6a, 0x04, 0x82, 0x04, 0x9a, 0x04, 0xb3, 0x04, + 0xcb, 0x04, 0xe4, 0x04, 0xfd, 0x04, 0x16, 0x05, 0x2f, 0x05, 0x48, 0x05, 0x61, 0x05, 0x7a, 0x05, 0x94, 0x05, 0xad, + 0x05, 0xc7, 0x05, 0xe1, 0x05, 0xfb, 0x05, 0x15, 0x06, 0x2f, 0x06, 0x49, 0x06, 0x63, 0x06, 0x7e, 0x06, 0x98, 0x06, + 0xb2, 0x06, 0xcd, 0x06, 0xe7, 0x06, 0x02, 0x07, 0x1d, 0x07, 0x37, 0x07, 0x52, 0x07, 0x6d, 0x07, 0x87, 0x07, 0xa2, + 0x07, 0xbd, 0x07, 0xd8, 0x07, 0xf3, 0x07, 0x0d, 0x08, 0x28, 0x08, 0x43, 0x08, 0x5e, 0x08, 0x79, 0x08, 0x93, 0x08, + 0xae, 0x08, 0xc9, 0x08, 0xe3, 0x08, 0xfe, 0x08, 0x19, 0x09, 0x33, 0x09, 0x4e, 0x09, 0x68, 0x09, 0x82, 0x09, 0x9d, + 0x09, 0xb7, 0x09, 0xd1, 0x09, 0xeb, 0x09, 0x05, 0x0a, 0x1f, 0x0a, 0x39, 0x0a, 0x53, 0x0a, 0x6c, 0x0a, 0x86, 0x0a, + 0x9f, 0x0a, 0xb8, 0x0a, 0xd1, 0x0a, 0xea, 0x0a, 0x03, 0x0b, 0x1c, 0x0b, 0x35, 0x0b, 0x4d, 0x0b, 0x66, 0x0b, 0x7e, + 0x0b, 0x96, 0x0b, 0xae, 0x0b, 0xc5, 0x0b, 0xdd, 0x0b, 0xf4, 0x0b, 0x0c, 0x0c, 0x23, 0x0c, 0x39, 0x0c, 0x50, 0x0c, + 0x67, 0x0c, 0x7d, 0x0c, 0x93, 0x0c, 0xa9, 0x0c, 0xbf, 0x0c, 0xd4, 0x0c, 0xe9, 0x0c, 0xfe, 0x0c, 0x13, 0x0d, 0x28, + 0x0d, 0x3c, 0x0d, 0x50, 0x0d, 0x64, 0x0d, 0x78, 0x0d, 0x8b, 0x0d, 0x9f, 0x0d, 0xb2, 0x0d, 0xc4, 0x0d, 0xd7, 0x0d, + 0xe9, 0x0d, 0xfb, 0x0d, 0x0d, 0x0e, 0x1e, 0x0e, 0x2f, 0x0e, 0x40, 0x0e, 0x51, 0x0e, 0x61, 0x0e, 0x71, 0x0e, 0x81, + 0x0e, 0x90, 0x0e, 0x9f, 0x0e, 0xae, 0x0e, 0xbd, 0x0e, 0xcb, 0x0e, 0xd9, 0x0e, 0xe7, 0x0e, 0xf4, 0x0e, 0x01, 0x0f, + 0x0e, 0x0f, 0x1b, 0x0f, 0x27, 0x0f, 0x33, 0x0f, 0x3e, 0x0f, 0x49, 0x0f, 0x54, 0x0f, 0x5f, 0x0f, 0x69, 0x0f, 0x73, + 0x0f, 0x7d, 0x0f, 0x86, 0x0f, 0x8f, 0x0f, 0x98, 0x0f, 0xa0, 0x0f, 0xa8, 0x0f, 0xaf, 0x0f, 0xb7, 0x0f, 0xbe, 0x0f, + 0xc4, 0x0f, 0xcb, 0x0f, 0xd0, 0x0f, 0xd6, 0x0f, 0xdb, 0x0f, 0xe0, 0x0f, 0xe5, 0x0f, 0xe9, 0x0f, 0xed, 0x0f, 0xf0, + 0x0f, 0xf3, 0x0f, 0xf6, 0x0f, 0xf9, 0x0f, 0xfb, 0x0f, 0xfc, 0x0f, 0xfe, 0x0f, 0xff, 0x0f, 0x00, 0x10, 0x00, 0x10, + 0x00, 0x10, 0x00, 0x10, 0xff, 0x0f, 0xfe, 0x0f, 0xfc, 0x0f, 0xfb, 0x0f, 0xf9, 0x0f, 0xf6, 0x0f, 0xf3, 0x0f, 0xf0, + 0x0f, 0xed, 0x0f, 0xe9, 0x0f, 0xe5, 0x0f, 0xe0, 0x0f, 0xdb, 0x0f, 0xd6, 0x0f, 0xd0, 0x0f, 0xcb, 0x0f, 0xc4, 0x0f, + 0xbe, 0x0f, 0xb7, 0x0f, 0xaf, 0x0f, 0xa8, 0x0f, 0xa0, 0x0f, 0x98, 0x0f, 0x8f, 0x0f, 0x86, 0x0f, 0x7d, 0x0f, 0x73, + 0x0f, 0x69, 0x0f, 0x5f, 0x0f, 0x54, 0x0f, 0x49, 0x0f, 0x3e, 0x0f, 0x33, 0x0f, 0x27, 0x0f, 0x1b, 0x0f, 0x0e, 0x0f, + 0x01, 0x0f, 0xf4, 0x0e, 0xe7, 0x0e, 0xd9, 0x0e, 0xcb, 0x0e, 0xbd, 0x0e, 0xae, 0x0e, 0x9f, 0x0e, 0x90, 0x0e, 0x81, + 0x0e, 0x71, 0x0e, 0x61, 0x0e, 0x51, 0x0e, 0x40, 0x0e, 0x2f, 0x0e, 0x1e, 0x0e, 0x0d, 0x0e, 0xfb, 0x0d, 0xe9, 0x0d, + 0xd7, 0x0d, 0xc4, 0x0d, 0xb2, 0x0d, 0x9f, 0x0d, 0x8b, 0x0d, 0x78, 0x0d, 0x64, 0x0d, 0x50, 0x0d, 0x3c, 0x0d, 0x28, + 0x0d, 0x13, 0x0d, 0xfe, 0x0c, 0xe9, 0x0c, 0xd4, 0x0c, 0xbf, 0x0c, 0xa9, 0x0c, 0x93, 0x0c, 0x7d, 0x0c, 0x67, 0x0c, + 0x50, 0x0c, 0x39, 0x0c, 0x23, 0x0c, 0x0c, 0x0c, 0xf4, 0x0b, 0xdd, 0x0b, 0xc5, 0x0b, 0xae, 0x0b, 0x96, 0x0b, 0x7e, + 0x0b, 0x66, 0x0b, 0x4d, 0x0b, 0x35, 0x0b, 0x1c, 0x0b, 0x03, 0x0b, 0xea, 0x0a, 0xd1, 0x0a, 0xb8, 0x0a, 0x9f, 0x0a, + 0x86, 0x0a, 0x6c, 0x0a, 0x53, 0x0a, 0x39, 0x0a, 0x1f, 0x0a, 0x05, 0x0a, 0xeb, 0x09, 0xd1, 0x09, 0xb7, 0x09, 0x9d, + 0x09, 0x82, 0x09, 0x68, 0x09, 0x4e, 0x09, 0x33, 0x09, 0x19, 0x09, 0xfe, 0x08, 0xe3, 0x08, 0xc9, 0x08, 0xae, 0x08, + 0x93, 0x08, 0x79, 0x08, 0x5e, 0x08, 0x43, 0x08, 0x28, 0x08, 0x0d, 0x08, 0xf3, 0x07, 0xd8, 0x07, 0xbd, 0x07, 0xa2, + 0x07, 0x87, 0x07, 0x6d, 0x07, 0x52, 0x07, 0x37, 0x07, 0x1d, 0x07, 0x02, 0x07, 0xe7, 0x06, 0xcd, 0x06, 0xb2, 0x06, + 0x98, 0x06, 0x7e, 0x06, 0x63, 0x06, 0x49, 0x06, 0x2f, 0x06, 0x15, 0x06, 0xfb, 0x05, 0xe1, 0x05, 0xc7, 0x05, 0xad, + 0x05, 0x94, 0x05, 0x7a, 0x05, 0x61, 0x05, 0x48, 0x05, 0x2f, 0x05, 0x16, 0x05, 0xfd, 0x04, 0xe4, 0x04, 0xcb, 0x04, + 0xb3, 0x04, 0x9a, 0x04, 0x82, 0x04, 0x6a, 0x04, 0x52, 0x04, 0x3b, 0x04, 0x23, 0x04, 0x0c, 0x04, 0xf4, 0x03, 0xdd, + 0x03, 0xc7, 0x03, 0xb0, 0x03, 0x99, 0x03, 0x83, 0x03, 0x6d, 0x03, 0x57, 0x03, 0x41, 0x03, 0x2c, 0x03, 0x17, 0x03, + 0x02, 0x03, 0xed, 0x02, 0xd8, 0x02, 0xc4, 0x02, 0xb0, 0x02, 0x9c, 0x02, 0x88, 0x02, 0x75, 0x02, 0x61, 0x02, 0x4e, + 0x02, 0x3c, 0x02, 0x29, 0x02, 0x17, 0x02, 0x05, 0x02, 0xf3, 0x01, 0xe2, 0x01, 0xd1, 0x01, 0xc0, 0x01, 0xaf, 0x01, + 0x9f, 0x01, 0x8f, 0x01, 0x7f, 0x01, 0x70, 0x01, 0x61, 0x01, 0x52, 0x01, 0x43, 0x01, 0x35, 0x01, 0x27, 0x01, 0x19, + 0x01, 0x0c, 0x01, 0xff, 0x00, 0xf2, 0x00, 0xe5, 0x00, 0xd9, 0x00, 0xcd, 0x00, 0xc2, 0x00, 0xb7, 0x00, 0xac, 0x00, + 0xa1, 0x00, 0x97, 0x00, 0x8d, 0x00, 0x83, 0x00, 0x7a, 0x00, 0x71, 0x00, 0x68, 0x00, 0x60, 0x00, 0x58, 0x00, 0x51, + 0x00, 0x49, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x30, 0x00, 0x2a, 0x00, 0x25, 0x00, 0x20, 0x00, 0x1b, 0x00, + 0x17, 0x00, 0x13, 0x00, 0x10, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xee, 0xff, 0xff, 0x38, 0xee, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x4d, 0x4c, + 0x49, 0x52, 0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0xf4, 0x05, 0x00, 0x00, 0xf8, 0x05, 0x00, + 0x00, 0xfc, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, + 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x68, 0x05, 0x00, 0x00, 0x24, 0x05, 0x00, 0x00, 0xc8, 0x04, 0x00, 0x00, 0x70, + 0x04, 0x00, 0x00, 0x4c, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0xc8, 0x03, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x00, + 0x4c, 0x03, 0x00, 0x00, 0x14, 0x03, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00, 0x6c, 0x01, 0x00, + 0x00, 0x48, 0x01, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0x78, 0x00, + 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf6, 0xfa, 0xff, 0xff, 0x0c, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x16, 0xfb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3a, 0xfb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0xa6, 0xfc, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x10, 0x00, 0x00, + 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x68, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xd6, 0xfc, 0xff, 0xff, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x98, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, + 0x00, 0x12, 0x00, 0x00, 0x00, 0x06, 0xfd, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x10, 0x00, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xc8, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x25, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0xfd, 0xff, 0xff, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, + 0x00, 0xf8, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00, + 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x1e, 0xfc, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x84, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x30, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x00, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x73, 0x63, 0x61, 0x6c, + 0x65, 0x00, 0x02, 0x24, 0x0f, 0x02, 0x01, 0x02, 0x03, 0x40, 0x04, 0x04, 0x04, 0x24, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xdc, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00, + 0x00, 0x24, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x73, 0x6e, + 0x72, 0x5f, 0x73, 0x68, 0x69, 0x66, 0x74, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x06, 0x04, 0x02, 0x24, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x20, 0xfd, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xec, 0x00, 0x00, + 0x00, 0x0a, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00, 0x00, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x5f, + 0x6f, 0x6e, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, + 0x00, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, + 0x67, 0x00, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x69, 0x6e, 0x67, 0x00, 0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x00, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x00, 0x6f, 0x6e, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x73, 0x6d, + 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x00, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x00, 0x73, + 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x00, 0x73, 0x70, 0x65, 0x63, 0x74, + 0x72, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x74, + 0x73, 0x00, 0x09, 0xa5, 0x88, 0x75, 0x6d, 0x59, 0x4d, 0x3a, 0x31, 0x23, 0x09, 0x00, 0x01, 0x00, 0x09, 0x00, 0x29, + 0x3c, 0xd7, 0x03, 0x00, 0x00, 0x33, 0x03, 0x28, 0x00, 0x67, 0x3e, 0x99, 0x01, 0x0a, 0x00, 0x0e, 0x00, 0x05, 0x05, + 0x69, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x1b, 0x25, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0xfe, 0xff, 0xff, 0x10, 0x00, + 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x54, 0xfe, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, + 0x00, 0x2c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x00, 0x01, 0x0e, 0x01, 0x01, 0x01, 0x28, 0x04, 0x02, 0x24, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x62, 0xfe, 0xff, + 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0xca, 0xff, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0a, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x8c, 0xf2, 0xff, 0xff, + 0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x0b, 0x00, + 0x04, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xd0, 0xf2, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0xfe, 0xfe, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x64, 0xff, 0xff, 0xff, 0x10, + 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x64, + 0x65, 0x78, 0x00, 0x02, 0x17, 0x0e, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0xf1, 0x00, 0x05, 0x00, 0x05, 0x05, + 0x06, 0x25, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x00, 0x00, 0xb8, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x54, 0x00, 0x66, 0x66, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, + 0x68, 0x00, 0x02, 0x0e, 0x0d, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x02, 0x05, 0x05, 0x06, 0x25, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, + 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, + 0x68, 0x69, 0x66, 0x74, 0x00, 0x01, 0x07, 0x01, 0x01, 0x01, 0x0c, 0x04, 0x02, 0x24, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0xc4, 0x0a, + 0x00, 0x00, 0x74, 0x0a, 0x00, 0x00, 0x3c, 0x0a, 0x00, 0x00, 0x04, 0x0a, 0x00, 0x00, 0xd0, 0x09, 0x00, 0x00, 0x88, + 0x09, 0x00, 0x00, 0x40, 0x09, 0x00, 0x00, 0xfc, 0x08, 0x00, 0x00, 0xb8, 0x08, 0x00, 0x00, 0x6c, 0x08, 0x00, 0x00, + 0x20, 0x08, 0x00, 0x00, 0xd4, 0x07, 0x00, 0x00, 0x88, 0x07, 0x00, 0x00, 0x3c, 0x07, 0x00, 0x00, 0xf8, 0x06, 0x00, + 0x00, 0xb8, 0x06, 0x00, 0x00, 0x84, 0x06, 0x00, 0x00, 0x50, 0x06, 0x00, 0x00, 0x1c, 0x06, 0x00, 0x00, 0xd8, 0x05, + 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x58, 0x05, 0x00, 0x00, 0x14, 0x05, 0x00, 0x00, 0xd8, 0x04, 0x00, 0x00, 0x98, + 0x04, 0x00, 0x00, 0x60, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0xb0, 0x03, 0x00, 0x00, + 0x6c, 0x03, 0x00, 0x00, 0x1c, 0x03, 0x00, 0x00, 0xc4, 0x02, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0x2c, 0x02, 0x00, + 0x00, 0xe4, 0x01, 0x00, 0x00, 0xac, 0x01, 0x00, 0x00, 0x78, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x08, 0x01, + 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xfe, + 0xf5, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x09, 0x20, 0x00, 0x00, 0x00, 0x44, 0xf5, 0xff, 0xff, 0x11, 0x00, 0x00, 0x00, 0x50, 0x61, 0x72, + 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x43, 0x61, 0x6c, 0x6c, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3e, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x84, 0xf5, 0xff, 0xff, + 0x0d, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x7a, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0xc0, + 0xf5, 0xff, 0xff, 0x15, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, + 0x00, 0xbe, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x04, 0xf6, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x61, + 0x64, 0x64, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xf2, 0xf6, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x18, 0x00, 0x00, 0x00, 0x38, 0xf6, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61, + 0x74, 0x65, 0x44, 0x69, 0x76, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2a, 0xf7, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x10, 0x00, 0x00, 0x00, 0x70, 0xf6, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x61, 0x64, 0x64, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x28, 0x00, 0x00, 0x00, 0x5a, 0xf7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, + 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0x00, 0xa0, 0xf6, 0xff, 0xff, 0x03, + 0x00, 0x00, 0x00, 0x6d, 0x75, 0x6c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x8a, 0xf7, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x14, 0x00, 0x00, 0x00, 0xd0, 0xf6, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, 0x74, 0x5f, 0x32, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xbe, 0xf7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, + 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x24, 0x00, 0x00, 0x00, + 0x04, 0xf7, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x6c, 0x6f, 0x67, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x02, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x22, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x18, 0x00, 0x00, 0x00, 0x48, 0xf7, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x63, 0x61, 0x6e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, + 0x00, 0x3a, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x21, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x38, 0x00, 0x00, 0x00, 0x80, 0xf7, 0xff, 0xff, 0x28, 0x00, 0x00, 0x00, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x73, + 0x70, 0x65, 0x63, 0x74, 0x72, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x31, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x92, 0xf8, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x34, + 0x00, 0x00, 0x00, 0xd8, 0xf7, 0xff, 0xff, 0x27, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x72, 0x61, 0x6c, + 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0xe6, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x2c, 0x00, 0x00, 0x00, 0x2c, 0xf8, 0xff, 0xff, 0x1e, 0x00, 0x00, 0x00, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, + 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x32, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x00, 0x78, 0xf8, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x72, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x14, 0x00, 0x00, 0x00, 0xb8, + 0xf8, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, 0x74, 0x5f, 0x31, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x00, 0x00, 0xa6, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, + 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xec, 0xf8, 0xff, 0xff, 0x06, 0x00, + 0x00, 0x00, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0xda, + 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x20, 0xf9, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, + 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xec, 0x00, + 0x00, 0x00, 0x16, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x5c, 0xf9, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, + 0x43, 0x61, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x4a, 0xfa, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0f, 0x1c, 0x00, 0x00, 0x00, 0x90, 0xf9, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x6c, 0x5f, 0x65, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, + 0x86, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x18, 0x00, 0x00, 0x00, 0xcc, 0xf9, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x66, 0x66, 0x74, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0xbe, + 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0x04, 0xfa, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x66, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x31, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x24, 0x00, 0x00, 0x00, 0x44, 0xfa, 0xff, 0xff, + 0x15, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x66, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f, + 0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x42, 0xfb, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00, 0x88, 0xfa, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x52, 0x65, 0x73, 0x68, + 0x61, 0x70, 0x65, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x76, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1c, 0x00, + 0x00, 0x00, 0xbc, 0xfa, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x77, 0x69, + 0x6e, 0x64, 0x6f, 0x77, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, + 0xb6, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xfc, 0xfa, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x43, 0x6f, + 0x6e, 0x73, 0x74, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, + 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, + 0x2c, 0xfb, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x16, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x11, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x5c, 0xfb, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x43, + 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, + 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, + 0x00, 0x8c, 0xfb, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x52, 0x65, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2f, 0x73, 0x68, + 0x61, 0x70, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x82, 0xfc, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x24, 0x00, 0x00, 0x00, 0xc8, 0xfb, 0xff, 0xff, 0x17, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x2f, 0x79, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc2, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x08, 0xfc, 0xff, 0xff, 0x18, 0x00, 0x00, 0x00, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, + 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x0a, 0xfd, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x50, 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, + 0x74, 0x5f, 0x31, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x52, 0xfd, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, + 0x00, 0x00, 0x00, 0x98, 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x32, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x9a, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0xe0, + 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x33, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x29, 0x00, 0x00, 0x00, 0xe2, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, + 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x28, 0xfd, 0xff, 0xff, 0x1a, + 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, + 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x34, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, + 0x00, 0x2a, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00, 0x00, 0x70, 0xfd, 0xff, 0xff, 0x11, 0x00, 0x00, 0x00, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x63, 0x61, 0x6e, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x6a, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, + 0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x00, 0xb0, 0xfd, + 0xff, 0xff, 0x13, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, + 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xaa, 0xfe, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x24, 0x00, 0x00, 0x00, 0xf0, 0xfd, 0xff, 0xff, 0x15, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, + 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xee, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0x34, 0xfe, 0xff, + 0xff, 0x15, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2f, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x32, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x78, 0xfe, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, + 0x74, 0x5f, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xa8, + 0xfe, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x73, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x96, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xdc, 0xfe, 0xff, 0xff, 0x07, 0x00, + 0x00, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x73, 0x5f, 0x31, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xca, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e, + 0x73, 0x74, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x1c, 0x00, + 0x18, 0x00, 0x17, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x2c, 0x00, 0x00, 0x00, 0x5c, 0xff, 0xff, 0xff, 0x1d, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f, + 0x66, 0x72, 0x61, 0x6d, 0x65, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, + 0x01, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1c, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00, + 0xa4, 0x01, 0x00, 0x00, 0x7c, 0x01, 0x00, 0x00, 0x68, 0x01, 0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, + 0x00, 0x10, 0x01, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x50, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x50, 0xfe, 0xff, 0xff, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x5c, 0xfe, 0xff, 0xff, + 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x68, 0xfe, 0xff, 0xff, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2a, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7c, 0xfe, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x12, 0x70, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x13, + 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, + 0x4c, 0x6f, 0x67, 0x00, 0x98, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x0a, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x50, 0x43, 0x41, 0x4e, 0x00, 0x00, 0xb8, 0xfe, + 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x23, 0x00, 0x00, 0x00, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, + 0x74, 0x72, 0x61, 0x6c, 0x53, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0xf0, 0xfe, 0xff, + 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x1a, 0x00, 0x00, 0x00, 0x53, 0x69, + 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x53, 0x71, 0x75, 0x61, 0x72, + 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x00, 0x00, 0x20, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x6c, 0xff, 0xff, 0xff, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x0c, 0x00, 0x10, 0x00, 0x0f, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x35, 0x7c, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x45, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x00, 0x00, + 0x00, 0x00, 0xa0, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0a, + 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x52, 0x66, 0x66, 0x74, 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff, + 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x12, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, + 0x6e, 0x61, 0x6c, 0x46, 0x66, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x0c, 0x00, + 0x0c, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x16, 0x0c, 0x00, 0x10, 0x00, 0x0f, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, + 0x6e, 0x61, 0x6c, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x00, 0x00, 0x00, 0x00}; + +} // namespace micro_wake_word +} // namespace esphome + +#endif // USE_ESP_IDF diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp new file mode 100644 index 0000000000..f637f8b2bb --- /dev/null +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -0,0 +1,507 @@ +#include "micro_wake_word.h" + +/** + * This is a workaround until we can figure out a way to get + * the tflite-micro idf component code available in CI + * + * */ +// +#ifndef CLANG_TIDY + +#ifdef USE_ESP_IDF + +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include "audio_preprocessor_int8_model_data.h" + +#include +#include +#include + +#include + +namespace esphome { +namespace micro_wake_word { + +static const char *const TAG = "micro_wake_word"; + +static const size_t SAMPLE_RATE_HZ = 16000; // 16 kHz +static const size_t BUFFER_LENGTH = 500; // 0.5 seconds +static const size_t BUFFER_SIZE = SAMPLE_RATE_HZ / 1000 * BUFFER_LENGTH; +static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000; // 32ms * 16kHz / 1000ms + +float MicroWakeWord::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } + +static const LogString *micro_wake_word_state_to_string(State state) { + switch (state) { + case State::IDLE: + return LOG_STR("IDLE"); + case State::START_MICROPHONE: + return LOG_STR("START_MICROPHONE"); + case State::STARTING_MICROPHONE: + return LOG_STR("STARTING_MICROPHONE"); + case State::DETECTING_WAKE_WORD: + return LOG_STR("DETECTING_WAKE_WORD"); + case State::STOP_MICROPHONE: + return LOG_STR("STOP_MICROPHONE"); + case State::STOPPING_MICROPHONE: + return LOG_STR("STOPPING_MICROPHONE"); + default: + return LOG_STR("UNKNOWN"); + } +} + +void MicroWakeWord::dump_config() { + ESP_LOGCONFIG(TAG, "microWakeWord:"); + ESP_LOGCONFIG(TAG, " Wake Word: %s", this->get_wake_word().c_str()); + ESP_LOGCONFIG(TAG, " Probability cutoff: %.3f", this->probability_cutoff_); + ESP_LOGCONFIG(TAG, " Sliding window size: %d", this->sliding_window_average_size_); +} + +void MicroWakeWord::setup() { + ESP_LOGCONFIG(TAG, "Setting up microWakeWord..."); + + if (!this->initialize_models()) { + ESP_LOGE(TAG, "Failed to initialize models"); + this->mark_failed(); + return; + } + + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->input_buffer_ = allocator.allocate(INPUT_BUFFER_SIZE * sizeof(int16_t)); + if (this->input_buffer_ == nullptr) { + ESP_LOGW(TAG, "Could not allocate input buffer"); + this->mark_failed(); + return; + } + + this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t)); + if (this->ring_buffer_ == nullptr) { + ESP_LOGW(TAG, "Could not allocate ring buffer"); + this->mark_failed(); + return; + } + + ESP_LOGCONFIG(TAG, "Micro Wake Word initialized"); +} + +int MicroWakeWord::read_microphone_() { + size_t bytes_read = this->microphone_->read(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t)); + if (bytes_read == 0) { + return 0; + } + + size_t bytes_free = this->ring_buffer_->free(); + + if (bytes_free < bytes_read) { + ESP_LOGW(TAG, + "Not enough free bytes in ring buffer to store incoming audio data (free bytes=%d, incoming bytes=%d). " + "Resetting the ring buffer. Wake word detection accuracy will be reduced.", + bytes_free, bytes_read); + + this->ring_buffer_->reset(); + } + + return this->ring_buffer_->write((void *) this->input_buffer_, bytes_read); +} + +void MicroWakeWord::loop() { + switch (this->state_) { + case State::IDLE: + break; + case State::START_MICROPHONE: + ESP_LOGD(TAG, "Starting Microphone"); + this->microphone_->start(); + this->set_state_(State::STARTING_MICROPHONE); + this->high_freq_.start(); + break; + case State::STARTING_MICROPHONE: + if (this->microphone_->is_running()) { + this->set_state_(State::DETECTING_WAKE_WORD); + } + break; + case State::DETECTING_WAKE_WORD: + this->read_microphone_(); + if (this->detect_wake_word_()) { + ESP_LOGD(TAG, "Wake Word Detected"); + this->detected_ = true; + this->set_state_(State::STOP_MICROPHONE); + } + break; + case State::STOP_MICROPHONE: + ESP_LOGD(TAG, "Stopping Microphone"); + this->microphone_->stop(); + this->set_state_(State::STOPPING_MICROPHONE); + this->high_freq_.stop(); + break; + case State::STOPPING_MICROPHONE: + if (this->microphone_->is_stopped()) { + this->set_state_(State::IDLE); + if (this->detected_) { + this->detected_ = false; + this->wake_word_detected_trigger_->trigger(this->wake_word_); + } + } + break; + } +} + +void MicroWakeWord::start() { + if (this->is_failed()) { + ESP_LOGW(TAG, "Wake word component is marked as failed. Please check setup logs"); + return; + } + if (this->state_ != State::IDLE) { + ESP_LOGW(TAG, "Wake word is already running"); + return; + } + this->set_state_(State::START_MICROPHONE); +} + +void MicroWakeWord::stop() { + if (this->state_ == State::IDLE) { + ESP_LOGW(TAG, "Wake word is already stopped"); + return; + } + if (this->state_ == State::STOPPING_MICROPHONE) { + ESP_LOGW(TAG, "Wake word is already stopping"); + return; + } + this->set_state_(State::STOP_MICROPHONE); +} + +void MicroWakeWord::set_state_(State state) { + ESP_LOGD(TAG, "State changed from %s to %s", LOG_STR_ARG(micro_wake_word_state_to_string(this->state_)), + LOG_STR_ARG(micro_wake_word_state_to_string(state))); + this->state_ = state; +} + +bool MicroWakeWord::initialize_models() { + ExternalRAMAllocator arena_allocator(ExternalRAMAllocator::ALLOW_FAILURE); + ExternalRAMAllocator features_allocator(ExternalRAMAllocator::ALLOW_FAILURE); + ExternalRAMAllocator audio_samples_allocator(ExternalRAMAllocator::ALLOW_FAILURE); + + this->streaming_tensor_arena_ = arena_allocator.allocate(STREAMING_MODEL_ARENA_SIZE); + if (this->streaming_tensor_arena_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate the streaming model's tensor arena."); + return false; + } + + this->streaming_var_arena_ = arena_allocator.allocate(STREAMING_MODEL_VARIABLE_ARENA_SIZE); + if (this->streaming_var_arena_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate the streaming model variable's tensor arena."); + return false; + } + + this->preprocessor_tensor_arena_ = arena_allocator.allocate(PREPROCESSOR_ARENA_SIZE); + if (this->preprocessor_tensor_arena_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate the audio preprocessor model's tensor arena."); + return false; + } + + this->new_features_data_ = features_allocator.allocate(PREPROCESSOR_FEATURE_SIZE); + if (this->new_features_data_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate the audio features buffer."); + return false; + } + + this->preprocessor_audio_buffer_ = audio_samples_allocator.allocate(SAMPLE_DURATION_COUNT); + if (this->preprocessor_audio_buffer_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate the audio preprocessor's buffer."); + return false; + } + + this->preprocessor_model_ = tflite::GetModel(G_AUDIO_PREPROCESSOR_INT8_TFLITE); + if (this->preprocessor_model_->version() != TFLITE_SCHEMA_VERSION) { + ESP_LOGE(TAG, "Wake word's audio preprocessor model's schema is not supported"); + return false; + } + + this->streaming_model_ = tflite::GetModel(this->model_start_); + if (this->streaming_model_->version() != TFLITE_SCHEMA_VERSION) { + ESP_LOGE(TAG, "Wake word's streaming model's schema is not supported"); + return false; + } + + static tflite::MicroMutableOpResolver<18> preprocessor_op_resolver; + static tflite::MicroMutableOpResolver<17> streaming_op_resolver; + + if (!this->register_preprocessor_ops_(preprocessor_op_resolver)) + return false; + if (!this->register_streaming_ops_(streaming_op_resolver)) + return false; + + tflite::MicroAllocator *ma = + tflite::MicroAllocator::Create(this->streaming_var_arena_, STREAMING_MODEL_VARIABLE_ARENA_SIZE); + this->mrv_ = tflite::MicroResourceVariables::Create(ma, 15); + + static tflite::MicroInterpreter static_preprocessor_interpreter( + this->preprocessor_model_, preprocessor_op_resolver, this->preprocessor_tensor_arena_, PREPROCESSOR_ARENA_SIZE); + + static tflite::MicroInterpreter static_streaming_interpreter(this->streaming_model_, streaming_op_resolver, + this->streaming_tensor_arena_, + STREAMING_MODEL_ARENA_SIZE, this->mrv_); + + this->preprocessor_interperter_ = &static_preprocessor_interpreter; + this->streaming_interpreter_ = &static_streaming_interpreter; + + // Allocate tensors for each models. + if (this->preprocessor_interperter_->AllocateTensors() != kTfLiteOk) { + ESP_LOGE(TAG, "Failed to allocate tensors for the audio preprocessor"); + return false; + } + if (this->streaming_interpreter_->AllocateTensors() != kTfLiteOk) { + ESP_LOGE(TAG, "Failed to allocate tensors for the streaming model"); + return false; + } + + // Verify input tensor matches expected values + TfLiteTensor *input = this->streaming_interpreter_->input(0); + if ((input->dims->size != 3) || (input->dims->data[0] != 1) || (input->dims->data[0] != 1) || + (input->dims->data[1] != 1) || (input->dims->data[2] != PREPROCESSOR_FEATURE_SIZE)) { + ESP_LOGE(TAG, "Wake word detection model tensor input dimensions is not 1x1x%u", input->dims->data[2]); + return false; + } + + if (input->type != kTfLiteInt8) { + ESP_LOGE(TAG, "Wake word detection model tensor input is not int8."); + return false; + } + + // Verify output tensor matches expected values + TfLiteTensor *output = this->streaming_interpreter_->output(0); + if ((output->dims->size != 2) || (output->dims->data[0] != 1) || (output->dims->data[1] != 1)) { + ESP_LOGE(TAG, "Wake word detection model tensor output dimensions is not 1x1."); + } + + if (output->type != kTfLiteUInt8) { + ESP_LOGE(TAG, "Wake word detection model tensor input is not uint8."); + return false; + } + + this->recent_streaming_probabilities_.resize(this->sliding_window_average_size_, 0.0); + + return true; +} + +bool MicroWakeWord::update_features_() { + // Retrieve strided audio samples + int16_t *audio_samples = nullptr; + if (!this->stride_audio_samples_(&audio_samples)) { + return false; + } + + // Compute the features for the newest audio samples + if (!this->generate_single_feature_(audio_samples, SAMPLE_DURATION_COUNT, this->new_features_data_)) { + return false; + } + + return true; +} + +float MicroWakeWord::perform_streaming_inference_() { + TfLiteTensor *input = this->streaming_interpreter_->input(0); + + size_t bytes_to_copy = input->bytes; + + memcpy((void *) (tflite::GetTensorData(input)), (const void *) (this->new_features_data_), bytes_to_copy); + + uint32_t prior_invoke = millis(); + + TfLiteStatus invoke_status = this->streaming_interpreter_->Invoke(); + if (invoke_status != kTfLiteOk) { + ESP_LOGW(TAG, "Streaming Interpreter Invoke failed"); + return false; + } + + ESP_LOGV(TAG, "Streaming Inference Latency=%u ms", (millis() - prior_invoke)); + + TfLiteTensor *output = this->streaming_interpreter_->output(0); + + return static_cast(output->data.uint8[0]) / 255.0; +} + +bool MicroWakeWord::detect_wake_word_() { + // Preprocess the newest audio samples into features + if (!this->update_features_()) { + return false; + } + + // Perform inference + float streaming_prob = this->perform_streaming_inference_(); + + // Add the most recent probability to the sliding window + this->recent_streaming_probabilities_[this->last_n_index_] = streaming_prob; + ++this->last_n_index_; + if (this->last_n_index_ == this->sliding_window_average_size_) + this->last_n_index_ = 0; + + float sum = 0.0; + for (auto &prob : this->recent_streaming_probabilities_) { + sum += prob; + } + + float sliding_window_average = sum / static_cast(this->sliding_window_average_size_); + + // Ensure we have enough samples since the last positive detection + this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0); + if (this->ignore_windows_ < 0) { + return false; + } + + // Detect the wake word if the sliding window average is above the cutoff + if (sliding_window_average > this->probability_cutoff_) { + this->ignore_windows_ = -MIN_SLICES_BEFORE_DETECTION; + for (auto &prob : this->recent_streaming_probabilities_) { + prob = 0; + } + + ESP_LOGD(TAG, "Wake word sliding average probability is %.3f and most recent probability is %.3f", + sliding_window_average, streaming_prob); + return true; + } + + return false; +} + +void MicroWakeWord::set_sliding_window_average_size(size_t size) { + this->sliding_window_average_size_ = size; + this->recent_streaming_probabilities_.resize(this->sliding_window_average_size_, 0.0); +} + +bool MicroWakeWord::slice_available_() { + size_t available = this->ring_buffer_->available(); + + return available > (NEW_SAMPLES_TO_GET * sizeof(int16_t)); +} + +bool MicroWakeWord::stride_audio_samples_(int16_t **audio_samples) { + if (!this->slice_available_()) { + return false; + } + + // Copy the last 320 bytes (160 samples over 10 ms) from the audio buffer to the start of the audio buffer + memcpy((void *) (this->preprocessor_audio_buffer_), (void *) (this->preprocessor_audio_buffer_ + NEW_SAMPLES_TO_GET), + HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t)); + + // Copy 640 bytes (320 samples over 20 ms) from the ring buffer into the audio buffer offset 320 bytes (160 samples + // over 10 ms) + size_t bytes_read = this->ring_buffer_->read((void *) (this->preprocessor_audio_buffer_ + HISTORY_SAMPLES_TO_KEEP), + NEW_SAMPLES_TO_GET * sizeof(int16_t), pdMS_TO_TICKS(200)); + + if (bytes_read == 0) { + ESP_LOGE(TAG, "Could not read data from Ring Buffer"); + } else if (bytes_read < NEW_SAMPLES_TO_GET * sizeof(int16_t)) { + ESP_LOGD(TAG, "Partial Read of Data by Model"); + ESP_LOGD(TAG, "Could only read %d bytes when required %d bytes ", bytes_read, + (int) (NEW_SAMPLES_TO_GET * sizeof(int16_t))); + return false; + } + + *audio_samples = this->preprocessor_audio_buffer_; + return true; +} + +bool MicroWakeWord::generate_single_feature_(const int16_t *audio_data, const int audio_data_size, + int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]) { + TfLiteTensor *input = this->preprocessor_interperter_->input(0); + TfLiteTensor *output = this->preprocessor_interperter_->output(0); + std::copy_n(audio_data, audio_data_size, tflite::GetTensorData(input)); + + if (this->preprocessor_interperter_->Invoke() != kTfLiteOk) { + ESP_LOGE(TAG, "Failed to preprocess audio for local wake word."); + return false; + } + std::memcpy(feature_output, tflite::GetTensorData(output), PREPROCESSOR_FEATURE_SIZE * sizeof(int8_t)); + + return true; +} + +bool MicroWakeWord::register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver) { + if (op_resolver.AddReshape() != kTfLiteOk) + return false; + if (op_resolver.AddCast() != kTfLiteOk) + return false; + if (op_resolver.AddStridedSlice() != kTfLiteOk) + return false; + if (op_resolver.AddConcatenation() != kTfLiteOk) + return false; + if (op_resolver.AddMul() != kTfLiteOk) + return false; + if (op_resolver.AddAdd() != kTfLiteOk) + return false; + if (op_resolver.AddDiv() != kTfLiteOk) + return false; + if (op_resolver.AddMinimum() != kTfLiteOk) + return false; + if (op_resolver.AddMaximum() != kTfLiteOk) + return false; + if (op_resolver.AddWindow() != kTfLiteOk) + return false; + if (op_resolver.AddFftAutoScale() != kTfLiteOk) + return false; + if (op_resolver.AddRfft() != kTfLiteOk) + return false; + if (op_resolver.AddEnergy() != kTfLiteOk) + return false; + if (op_resolver.AddFilterBank() != kTfLiteOk) + return false; + if (op_resolver.AddFilterBankSquareRoot() != kTfLiteOk) + return false; + if (op_resolver.AddFilterBankSpectralSubtraction() != kTfLiteOk) + return false; + if (op_resolver.AddPCAN() != kTfLiteOk) + return false; + if (op_resolver.AddFilterBankLog() != kTfLiteOk) + return false; + + return true; +} + +bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver) { + if (op_resolver.AddCallOnce() != kTfLiteOk) + return false; + if (op_resolver.AddVarHandle() != kTfLiteOk) + return false; + if (op_resolver.AddReshape() != kTfLiteOk) + return false; + if (op_resolver.AddReadVariable() != kTfLiteOk) + return false; + if (op_resolver.AddStridedSlice() != kTfLiteOk) + return false; + if (op_resolver.AddConcatenation() != kTfLiteOk) + return false; + if (op_resolver.AddAssignVariable() != kTfLiteOk) + return false; + if (op_resolver.AddConv2D() != kTfLiteOk) + return false; + if (op_resolver.AddMul() != kTfLiteOk) + return false; + if (op_resolver.AddAdd() != kTfLiteOk) + return false; + if (op_resolver.AddMean() != kTfLiteOk) + return false; + if (op_resolver.AddFullyConnected() != kTfLiteOk) + return false; + if (op_resolver.AddLogistic() != kTfLiteOk) + return false; + if (op_resolver.AddQuantize() != kTfLiteOk) + return false; + if (op_resolver.AddDepthwiseConv2D() != kTfLiteOk) + return false; + if (op_resolver.AddAveragePool2D() != kTfLiteOk) + return false; + if (op_resolver.AddMaxPool2D() != kTfLiteOk) + return false; + + return true; +} + +} // namespace micro_wake_word +} // namespace esphome + +#endif // USE_ESP_IDF + +#endif // CLANG_TIDY diff --git a/esphome/components/micro_wake_word/micro_wake_word.h b/esphome/components/micro_wake_word/micro_wake_word.h new file mode 100644 index 0000000000..1d7c18d686 --- /dev/null +++ b/esphome/components/micro_wake_word/micro_wake_word.h @@ -0,0 +1,206 @@ +#pragma once + +/** + * This is a workaround until we can figure out a way to get + * the tflite-micro idf component code available in CI + * + * */ +// +#ifndef CLANG_TIDY + +#ifdef USE_ESP_IDF + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/ring_buffer.h" + +#include "esphome/components/microphone/microphone.h" + +#include +#include +#include + +namespace esphome { +namespace micro_wake_word { + +// The following are dictated by the preprocessor model +// +// The number of features the audio preprocessor generates per slice +static const uint8_t PREPROCESSOR_FEATURE_SIZE = 40; +// How frequently the preprocessor generates a new set of features +static const uint8_t FEATURE_STRIDE_MS = 20; +// Duration of each slice used as input into the preprocessor +static const uint8_t FEATURE_DURATION_MS = 30; +// Audio sample frequency in hertz +static const uint16_t AUDIO_SAMPLE_FREQUENCY = 16000; +// The number of old audio samples that are saved to be part of the next feature window +static const uint16_t HISTORY_SAMPLES_TO_KEEP = + ((FEATURE_DURATION_MS - FEATURE_STRIDE_MS) * (AUDIO_SAMPLE_FREQUENCY / 1000)); +// The number of new audio samples to receive to be included with the next feature window +static const uint16_t NEW_SAMPLES_TO_GET = (FEATURE_STRIDE_MS * (AUDIO_SAMPLE_FREQUENCY / 1000)); +// The total number of audio samples included in the feature window +static const uint16_t SAMPLE_DURATION_COUNT = FEATURE_DURATION_MS * AUDIO_SAMPLE_FREQUENCY / 1000; +// Number of bytes in memory needed for the preprocessor arena +static const uint32_t PREPROCESSOR_ARENA_SIZE = 9528; + +// The following configure the streaming wake word model +// +// The number of audio slices to process before accepting a positive detection +static const uint8_t MIN_SLICES_BEFORE_DETECTION = 74; + +// Number of bytes in memory needed for the streaming wake word model +static const uint32_t STREAMING_MODEL_ARENA_SIZE = 64000; +static const uint32_t STREAMING_MODEL_VARIABLE_ARENA_SIZE = 1024; + +enum State { + IDLE, + START_MICROPHONE, + STARTING_MICROPHONE, + DETECTING_WAKE_WORD, + STOP_MICROPHONE, + STOPPING_MICROPHONE, +}; + +class MicroWakeWord : public Component { + public: + void setup() override; + void loop() override; + float get_setup_priority() const override; + void dump_config() override; + + void start(); + void stop(); + + bool is_running() const { return this->state_ != State::IDLE; } + + bool initialize_models(); + + std::string get_wake_word() { return this->wake_word_; } + + // Increasing either of these will reduce the rate of false acceptances while increasing the false rejection rate + void set_probability_cutoff(float probability_cutoff) { this->probability_cutoff_ = probability_cutoff; } + void set_sliding_window_average_size(size_t size); + + void set_microphone(microphone::Microphone *microphone) { this->microphone_ = microphone; } + + Trigger *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; } + + void set_model_start(const uint8_t *model_start) { this->model_start_ = model_start; } + void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; } + + protected: + void set_state_(State state); + int read_microphone_(); + + const uint8_t *model_start_; + std::string wake_word_; + + microphone::Microphone *microphone_{nullptr}; + Trigger *wake_word_detected_trigger_ = new Trigger(); + State state_{State::IDLE}; + HighFrequencyLoopRequester high_freq_; + + std::unique_ptr ring_buffer_; + + int16_t *input_buffer_; + + const tflite::Model *preprocessor_model_{nullptr}; + const tflite::Model *streaming_model_{nullptr}; + tflite::MicroInterpreter *streaming_interpreter_{nullptr}; + tflite::MicroInterpreter *preprocessor_interperter_{nullptr}; + + std::vector recent_streaming_probabilities_; + size_t last_n_index_{0}; + + float probability_cutoff_{0.5}; + size_t sliding_window_average_size_{10}; + + // When the wake word detection first starts or after the word has been detected once, we ignore this many audio + // feature slices before accepting a positive detection again + int16_t ignore_windows_{-MIN_SLICES_BEFORE_DETECTION}; + + uint8_t *streaming_var_arena_{nullptr}; + uint8_t *streaming_tensor_arena_{nullptr}; + uint8_t *preprocessor_tensor_arena_{nullptr}; + int8_t *new_features_data_{nullptr}; + + tflite::MicroResourceVariables *mrv_{nullptr}; + + // Stores audio fed into feature generator preprocessor + int16_t *preprocessor_audio_buffer_; + + bool detected_{false}; + + /** Detects if wake word has been said + * + * If enough audio samples are available, it will generate one slice of new features. + * If the streaming model predicts the wake word, then the nonstreaming model confirms it. + * @param ring_Buffer Ring buffer containing raw audio samples + * @return True if the wake word is detected, false otherwise + */ + bool detect_wake_word_(); + + /// @brief Returns true if there are enough audio samples in the buffer to generate another slice of features + bool slice_available_(); + + /** Shifts previous feature slices over by one and generates a new slice of features + * + * @param ring_buffer ring buffer containing raw audio samples + * @return True if a new slice of features was generated, false otherwise + */ + bool update_features_(); + + /** Generates features from audio samples + * + * Adapted from TFLite micro speech example + * @param audio_data Pointer to array with the audio samples + * @param audio_data_size The number of samples to use as input to the preprocessor model + * @param feature_output Array that will store the features + * @return True if successful, false otherwise. + */ + bool generate_single_feature_(const int16_t *audio_data, int audio_data_size, + int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]); + + /** Performs inference over the most recent feature slice with the streaming model + * + * @return Probability of the wake word between 0.0 and 1.0 + */ + float perform_streaming_inference_(); + + /** Strides the audio samples by keeping the last 10 ms of the previous slice + * + * Adapted from the TFLite micro speech example + * @param ring_buffer Ring buffer containing raw audio samples + * @param audio_samples Pointer to an array that will store the strided audio samples + * @return True if successful, false otherwise + */ + bool stride_audio_samples_(int16_t **audio_samples); + + /// @brief Returns true if successfully registered the preprocessor's TensorFlow operations + bool register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver); + + /// @brief Returns true if successfully registered the streaming model's TensorFlow operations + bool register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver); +}; + +template class StartAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->start(); } +}; + +template class StopAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->stop(); } +}; + +template class IsRunningCondition : public Condition, public Parented { + public: + bool check(Ts... x) override { return this->parent_->is_running(); } +}; + +} // namespace micro_wake_word +} // namespace esphome + +#endif // USE_ESP_IDF + +#endif // CLANG_TIDY diff --git a/esphome/components/microphone/automation.h b/esphome/components/microphone/automation.h index 5313f07f72..29c0ec5df2 100644 --- a/esphome/components/microphone/automation.h +++ b/esphome/components/microphone/automation.h @@ -23,7 +23,7 @@ class DataTrigger : public Trigger &> { } }; -template class IsCapturingActon : public Condition, public Parented { +template class IsCapturingCondition : public Condition, public Parented { public: bool check(Ts... x) override { return this->parent_->is_running(); } }; diff --git a/esphome/components/mitsubishi/climate.py b/esphome/components/mitsubishi/climate.py index 0df17a5d16..5e865c636f 100644 --- a/esphome/components/mitsubishi/climate.py +++ b/esphome/components/mitsubishi/climate.py @@ -9,9 +9,53 @@ AUTO_LOAD = ["climate_ir"] mitsubishi_ns = cg.esphome_ns.namespace("mitsubishi") MitsubishiClimate = mitsubishi_ns.class_("MitsubishiClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_SCHEMA.extend( +CONF_SET_FAN_MODE = "set_fan_mode" +SetFanMode = mitsubishi_ns.enum("SetFanMode") +SETFANMODE = { + "quiet_4levels": SetFanMode.MITSUBISHI_FAN_Q4L, + # "5levels": SetFanMode.MITSUBISHI_FAN_5L, + "4levels": SetFanMode.MITSUBISHI_FAN_4L, + "3levels": SetFanMode.MITSUBISHI_FAN_3L, +} + +CONF_SUPPORTS_DRY = "supports_dry" +CONF_SUPPORTS_FAN_ONLY = "supports_fan_only" + +CONF_HORIZONTAL_DEFAULT = "horizontal_default" +HorizontalDirections = mitsubishi_ns.enum("HorizontalDirections") +HORIZONTAL_DIRECTIONS = { + "left": HorizontalDirections.HORIZONTAL_DIRECTION_LEFT, + "middle-left": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE_LEFT, + "middle": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE, + "middle-right": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE_RIGHT, + "right": HorizontalDirections.HORIZONTAL_DIRECTION_RIGHT, + "split": HorizontalDirections.HORIZONTAL_DIRECTION_SPLIT, +} + +CONF_VERTICAL_DEFAULT = "vertical_default" +VerticalDirections = mitsubishi_ns.enum("VerticalDirections") +VERTICAL_DIRECTIONS = { + "auto": VerticalDirections.VERTICAL_DIRECTION_AUTO, + "up": VerticalDirections.VERTICAL_DIRECTION_UP, + "middle-up": VerticalDirections.VERTICAL_DIRECTION_MIDDLE_UP, + "middle": VerticalDirections.VERTICAL_DIRECTION_MIDDLE, + "middle-down": VerticalDirections.VERTICAL_DIRECTION_MIDDLE_DOWN, + "down": VerticalDirections.VERTICAL_DIRECTION_DOWN, +} + + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(MitsubishiClimate), + cv.Optional(CONF_SET_FAN_MODE, default="3levels"): cv.enum(SETFANMODE), + cv.Optional(CONF_SUPPORTS_DRY, default=False): cv.boolean, + cv.Optional(CONF_SUPPORTS_FAN_ONLY, default=False): cv.boolean, + cv.Optional(CONF_HORIZONTAL_DEFAULT, default="middle"): cv.enum( + HORIZONTAL_DIRECTIONS + ), + cv.Optional(CONF_VERTICAL_DEFAULT, default="middle"): cv.enum( + VERTICAL_DIRECTIONS + ), } ) @@ -19,3 +63,9 @@ CONFIG_SCHEMA = climate_ir.CLIMATE_IR_SCHEMA.extend( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await climate_ir.register_climate_ir(var, config) + + cg.add(var.set_fan_mode(config[CONF_SET_FAN_MODE])) + cg.add(var.set_supports_dry(config[CONF_SUPPORTS_DRY])) + cg.add(var.set_supports_fan_only(config[CONF_SUPPORTS_FAN_ONLY])) + cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT])) + cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT])) diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index 87b78128e4..081c24a050 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -8,12 +8,31 @@ static const char *const TAG = "mitsubishi.climate"; const uint32_t MITSUBISHI_OFF = 0x00; -const uint8_t MITSUBISHI_COOL = 0x18; -const uint8_t MITSUBISHI_DRY = 0x10; -const uint8_t MITSUBISHI_AUTO = 0x20; -const uint8_t MITSUBISHI_HEAT = 0x08; +const uint8_t MITSUBISHI_MODE_AUTO = 0x20; +const uint8_t MITSUBISHI_MODE_COOL = 0x18; +const uint8_t MITSUBISHI_MODE_DRY = 0x10; +const uint8_t MITSUBISHI_MODE_FAN_ONLY = 0x38; +const uint8_t MITSUBISHI_MODE_HEAT = 0x08; + +const uint8_t MITSUBISHI_MODE_A_HEAT = 0x00; +const uint8_t MITSUBISHI_MODE_A_DRY = 0x02; +const uint8_t MITSUBISHI_MODE_A_COOL = 0x06; +const uint8_t MITSUBISHI_MODE_A_AUTO = 0x06; + +const uint8_t MITSUBISHI_WIDE_VANE_SWING = 0xC0; + const uint8_t MITSUBISHI_FAN_AUTO = 0x00; +const uint8_t MITSUBISHI_VERTICAL_VANE_SWING = 0x38; + +// const uint8_t MITSUBISHI_AUTO = 0X80; +const uint8_t MITSUBISHI_OTHERWISE = 0X40; +const uint8_t MITSUBISHI_POWERFUL = 0x08; + +// Optional presets used to enable some model features +const uint8_t MITSUBISHI_ECONOCOOL = 0x20; +const uint8_t MITSUBISHI_NIGHTMODE = 0xC1; + // Pulse parameters in usec const uint16_t MITSUBISHI_BIT_MARK = 430; const uint16_t MITSUBISHI_ONE_SPACE = 1250; @@ -22,19 +41,97 @@ const uint16_t MITSUBISHI_HEADER_MARK = 3500; const uint16_t MITSUBISHI_HEADER_SPACE = 1700; const uint16_t MITSUBISHI_MIN_GAP = 17500; +// Marker bytes +const uint8_t MITSUBISHI_BYTE00 = 0X23; +const uint8_t MITSUBISHI_BYTE01 = 0XCB; +const uint8_t MITSUBISHI_BYTE02 = 0X26; +const uint8_t MITSUBISHI_BYTE03 = 0X01; +const uint8_t MITSUBISHI_BYTE04 = 0X00; +const uint8_t MITSUBISHI_BYTE13 = 0X00; +const uint8_t MITSUBISHI_BYTE16 = 0X00; + +climate::ClimateTraits MitsubishiClimate::traits() { + auto traits = climate::ClimateTraits(); + traits.set_supports_action(false); + traits.set_visual_min_temperature(MITSUBISHI_TEMP_MIN); + traits.set_visual_max_temperature(MITSUBISHI_TEMP_MAX); + traits.set_visual_temperature_step(1.0f); + traits.set_supported_modes({climate::CLIMATE_MODE_OFF}); + + if (this->supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (this->supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + + if (this->supports_cool_ && this->supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); + + if (this->supports_dry_) + traits.add_supported_mode(climate::CLIMATE_MODE_DRY); + if (this->supports_fan_only_) + traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); + + // Default to only 3 levels in ESPHome even if most unit supports 4. The 3rd level is not used. + traits.set_supported_fan_modes( + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}); + if (this->fan_mode_ == MITSUBISHI_FAN_Q4L) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_QUIET); + if (/*this->fan_mode_ == MITSUBISHI_FAN_5L ||*/ this->fan_mode_ >= MITSUBISHI_FAN_4L) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE); // Shouldn't be used for this but it helps + + traits.set_supported_swing_modes({climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, + climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL}); + + traits.set_supported_presets({climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO, + climate::CLIMATE_PRESET_BOOST, climate::CLIMATE_PRESET_SLEEP}); + + return traits; +} + void MitsubishiClimate::transmit_state() { - uint32_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x30, - 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // Byte 0-4: Constant: 0x23, 0xCB, 0x26, 0x01, 0x00 + // Byte 5: On=0x20, Off: 0x00 + // Byte 6: MODE (See MODEs above (Heat/Dry/Cool/Auto/FanOnly) + // Byte 7: TEMP bits 0,1,2,3, added to MITSUBISHI_TEMP_MIN + // Example: 0x00 = 0°C+MITSUBISHI_TEMP_MIN = 16°C; 0x07 = 7°C+MITSUBISHI_TEMP_MIN = 23°C + // Byte 8: MODE_A & Wide Vane (if present) + // MODE_A bits 0,1,2 different than Byte 6 (See MODE_As above) + // Wide Vane bits 4,5,6,7 (Middle = 0x30) + // Byte 9: FAN/Vertical Vane/Switch To Auto + // FAN (Speed) bits 0,1,2 + // Vertical Vane bits 3,4,5 (Auto = 0x00) + // Switch To Auto bits 6,7 + // Byte 10: CLOCK Current time as configured on remote (0x00=Not used) + // Byte 11: END CLOCK Stop time of HVAC (0x00 for no setting) + // Byte 12: START CLOCK Start time of HVAC (0x00 for no setting) + // Byte 13: Constant 0x00 + // Byte 14: HVAC specfic, i.e. ECONO COOL, CLEAN MODE, always 0x00 + // Byte 15: HVAC specfic, i.e. POWERFUL, SMART SET, PLASMA, always 0x00 + // Byte 16: Constant 0x00 + // Byte 17: Checksum: SUM[Byte0...Byte16] + uint32_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; switch (this->mode) { - case climate::CLIMATE_MODE_COOL: - remote_state[6] = MITSUBISHI_COOL; - break; case climate::CLIMATE_MODE_HEAT: - remote_state[6] = MITSUBISHI_HEAT; + remote_state[6] = MITSUBISHI_MODE_HEAT; + remote_state[8] = MITSUBISHI_MODE_A_HEAT; + break; + case climate::CLIMATE_MODE_DRY: + remote_state[6] = MITSUBISHI_MODE_DRY; + remote_state[8] = MITSUBISHI_MODE_A_DRY; + break; + case climate::CLIMATE_MODE_COOL: + remote_state[6] = MITSUBISHI_MODE_COOL; + remote_state[8] = MITSUBISHI_MODE_A_COOL; break; case climate::CLIMATE_MODE_HEAT_COOL: - remote_state[6] = MITSUBISHI_AUTO; + remote_state[6] = MITSUBISHI_MODE_AUTO; + remote_state[8] = MITSUBISHI_MODE_A_AUTO; + break; + case climate::CLIMATE_MODE_FAN_ONLY: + remote_state[6] = MITSUBISHI_MODE_FAN_ONLY; + remote_state[8] = MITSUBISHI_MODE_A_AUTO; break; case climate::CLIMATE_MODE_OFF: default: @@ -42,17 +139,111 @@ void MitsubishiClimate::transmit_state() { break; } - remote_state[7] = (uint8_t) roundf(clamp(this->target_temperature, MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) - - MITSUBISHI_TEMP_MIN); + // Temperature + if (this->mode == climate::CLIMATE_MODE_DRY) { + remote_state[7] = 24 - MITSUBISHI_TEMP_MIN; // Remote sends always 24°C if "Dry" mode is selected + } else { + remote_state[7] = (uint8_t) roundf( + clamp(this->target_temperature, MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) - MITSUBISHI_TEMP_MIN); + } - ESP_LOGV(TAG, "Sending Mitsubishi target temp: %.1f state: %02" PRIX32 " mode: %02" PRIX32 " temp: %02" PRIX32, - this->target_temperature, remote_state[5], remote_state[6], remote_state[7]); + // Wide Vane + switch (this->swing_mode) { + case climate::CLIMATE_SWING_HORIZONTAL: + case climate::CLIMATE_SWING_BOTH: + remote_state[8] = remote_state[8] | MITSUBISHI_WIDE_VANE_SWING; // Wide Vane Swing + break; + case climate::CLIMATE_SWING_OFF: + default: + remote_state[8] = remote_state[8] | this->default_horizontal_direction_; // Off--> horizontal default position + break; + } + + ESP_LOGD(TAG, "default_horizontal_direction_: %02X", this->default_horizontal_direction_); + + // Fan Speed & Vertical Vane + // Map of Climate fan mode to this device expected value + // For 3Level: Low = 1, Medium = 2, High = 3 + // For 4Level: Low = 1, Middle = 2, Medium = 3, High = 4 + // For 5Level: Low = 1, Middle = 2, Medium = 3, High = 4 + // For 4Level + Quiet: Low = 1, Middle = 2, Medium = 3, High = 4, Quiet = 5 + + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_LOW: + remote_state[9] = 1; + break; + case climate::CLIMATE_FAN_MEDIUM: + if (this->fan_mode_ == MITSUBISHI_FAN_3L) { + remote_state[9] = 2; + } else { + remote_state[9] = 3; + } + break; + case climate::CLIMATE_FAN_HIGH: + if (this->fan_mode_ == MITSUBISHI_FAN_3L) { + remote_state[9] = 3; + } else { + remote_state[9] = 4; + } + break; + case climate::CLIMATE_FAN_MIDDLE: + remote_state[9] = 2; + break; + case climate::CLIMATE_FAN_QUIET: + remote_state[9] = 5; + break; + default: + remote_state[9] = MITSUBISHI_FAN_AUTO; + break; + } + + ESP_LOGD(TAG, "fan: %02x state: %02x", this->fan_mode.value(), remote_state[9]); + + // Vertical Vane + switch (this->swing_mode) { + case climate::CLIMATE_SWING_VERTICAL: + case climate::CLIMATE_SWING_BOTH: + remote_state[9] = remote_state[9] | MITSUBISHI_VERTICAL_VANE_SWING | MITSUBISHI_OTHERWISE; // Vane Swing + break; + case climate::CLIMATE_SWING_OFF: + default: + remote_state[9] = remote_state[9] | this->default_vertical_direction_ | + MITSUBISHI_OTHERWISE; // Off--> vertical default position + break; + } + + ESP_LOGD(TAG, "default_vertical_direction_: %02X", this->default_vertical_direction_); + + // Special modes + switch (this->preset.value()) { + case climate::CLIMATE_PRESET_ECO: + remote_state[6] = MITSUBISHI_MODE_COOL | MITSUBISHI_OTHERWISE; + remote_state[8] = (remote_state[8] & ~7) | MITSUBISHI_MODE_A_COOL; + remote_state[14] = MITSUBISHI_ECONOCOOL; + break; + case climate::CLIMATE_PRESET_SLEEP: + remote_state[9] = MITSUBISHI_FAN_AUTO; + remote_state[14] = MITSUBISHI_NIGHTMODE; + break; + case climate::CLIMATE_PRESET_BOOST: + remote_state[6] |= MITSUBISHI_OTHERWISE; + remote_state[15] = MITSUBISHI_POWERFUL; + break; + case climate::CLIMATE_PRESET_NONE: + default: + break; + } // Checksum for (int i = 0; i < 17; i++) { remote_state[17] += remote_state[i]; } + ESP_LOGD(TAG, "sending: %02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X", + remote_state[0], remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], + remote_state[6], remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], + remote_state[12], remote_state[13], remote_state[14], remote_state[15], remote_state[16], remote_state[17]); + auto transmit = this->transmitter_->transmit(); auto *data = transmit.get_data(); @@ -81,5 +272,119 @@ void MitsubishiClimate::transmit_state() { transmit.perform(); } +bool MitsubishiClimate::parse_state_frame_(const uint8_t frame[]) { return false; } + +bool MitsubishiClimate::on_receive(remote_base::RemoteReceiveData data) { + uint8_t state_frame[18] = {}; + + if (!data.expect_item(MITSUBISHI_HEADER_MARK, MITSUBISHI_HEADER_SPACE)) { + ESP_LOGV(TAG, "Header fail"); + return false; + } + + for (uint8_t pos = 0; pos < 18; pos++) { + uint8_t byte = 0; + for (int8_t bit = 0; bit < 8; bit++) { + if (data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ONE_SPACE)) { + byte |= 1 << bit; + } else if (!data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ZERO_SPACE)) { + ESP_LOGV(TAG, "Byte %d bit %d fail", pos, bit); + return false; + } + } + state_frame[pos] = byte; + + // Check Header && Footer + if ((pos == 0 && byte != MITSUBISHI_BYTE00) || (pos == 1 && byte != MITSUBISHI_BYTE01) || + (pos == 2 && byte != MITSUBISHI_BYTE02) || (pos == 3 && byte != MITSUBISHI_BYTE03) || + (pos == 4 && byte != MITSUBISHI_BYTE04) || (pos == 13 && byte != MITSUBISHI_BYTE13) || + (pos == 16 && byte != MITSUBISHI_BYTE16)) { + ESP_LOGV(TAG, "Bytes 0,1,2,3,4,13 or 16 fail - invalid value"); + return false; + } + } + + // On/Off and Mode + if (state_frame[5] == MITSUBISHI_OFF) { + this->mode = climate::CLIMATE_MODE_OFF; + } else { + switch (state_frame[6]) { + case MITSUBISHI_MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + break; + case MITSUBISHI_MODE_DRY: + this->mode = climate::CLIMATE_MODE_DRY; + break; + case MITSUBISHI_MODE_COOL: + this->mode = climate::CLIMATE_MODE_COOL; + break; + case MITSUBISHI_MODE_FAN_ONLY: + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + break; + case MITSUBISHI_MODE_AUTO: + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + break; + } + } + + // Temp + this->target_temperature = state_frame[7] + MITSUBISHI_TEMP_MIN; + + // Fan + uint8_t fan = state_frame[9] & 0x07; //(Bit 0,1,2 = Speed) + // Map of Climate fan mode to this device expected value + // For 3Level: Low = 1, Medium = 2, High = 3 + // For 4Level: Low = 1, Middle = 2, Medium = 3, High = 4 + // For 5Level: Low = 1, Middle = 2, Medium = 3, High = 4 + // For 4Level + Quiet: Low = 1, Middle = 2, Medium = 3, High = 4, Quiet = 5 + climate::ClimateFanMode modes_mapping[8] = { + climate::CLIMATE_FAN_AUTO, + climate::CLIMATE_FAN_LOW, + this->fan_mode_ == MITSUBISHI_FAN_3L ? climate::CLIMATE_FAN_MEDIUM : climate::CLIMATE_FAN_MIDDLE, + this->fan_mode_ == MITSUBISHI_FAN_3L ? climate::CLIMATE_FAN_HIGH : climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH, + climate::CLIMATE_FAN_QUIET, + climate::CLIMATE_FAN_AUTO, + climate::CLIMATE_FAN_AUTO}; + this->fan_mode = modes_mapping[fan]; + + // Wide Vane + uint8_t wide_vane = state_frame[8] & 0xF0; // Bits 4,5,6,7 + switch (wide_vane) { + case MITSUBISHI_WIDE_VANE_SWING: + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + break; + default: + this->swing_mode = climate::CLIMATE_SWING_OFF; + break; + } + + // Vertical Vane + uint8_t vertical_vane = state_frame[9] & 0x38; // Bits 3,4,5 + switch (vertical_vane) { + case MITSUBISHI_VERTICAL_VANE_SWING: + if (this->swing_mode == climate::CLIMATE_SWING_HORIZONTAL) { + this->swing_mode = climate::CLIMATE_SWING_BOTH; + } else { + this->swing_mode = climate::CLIMATE_SWING_VERTICAL; + } + break; + } + + switch (state_frame[14]) { + case MITSUBISHI_ECONOCOOL: + this->preset = climate::CLIMATE_PRESET_ECO; + break; + case MITSUBISHI_NIGHTMODE: + this->preset = climate::CLIMATE_PRESET_SLEEP; + break; + } + + ESP_LOGV(TAG, "Receiving: %s", format_hex_pretty(state_frame, 18).c_str()); + + this->publish_state(); + return true; +} + } // namespace mitsubishi } // namespace esphome diff --git a/esphome/components/mitsubishi/mitsubishi.h b/esphome/components/mitsubishi/mitsubishi.h index 9a88040d3f..cfe12428da 100644 --- a/esphome/components/mitsubishi/mitsubishi.h +++ b/esphome/components/mitsubishi/mitsubishi.h @@ -11,13 +11,72 @@ namespace mitsubishi { const uint8_t MITSUBISHI_TEMP_MIN = 16; // Celsius const uint8_t MITSUBISHI_TEMP_MAX = 31; // Celsius +// Fan mode +enum SetFanMode { + MITSUBISHI_FAN_3L = 0, // 3 levels + auto + MITSUBISHI_FAN_4L, // 4 levels + auto + MITSUBISHI_FAN_Q4L, // Quiet + 4 levels + auto + // MITSUBISHI_FAN_5L, // 5 levels + auto +}; + +// Enum to represent horizontal directios +enum HorizontalDirection { + HORIZONTAL_DIRECTION_LEFT = 0x10, + HORIZONTAL_DIRECTION_MIDDLE_LEFT = 0x20, + HORIZONTAL_DIRECTION_MIDDLE = 0x30, + HORIZONTAL_DIRECTION_MIDDLE_RIGHT = 0x40, + HORIZONTAL_DIRECTION_RIGHT = 0x50, + HORIZONTAL_DIRECTION_SPLIT = 0x80, +}; + +// Enum to represent vertical directions +enum VerticalDirection { + VERTICAL_DIRECTION_AUTO = 0x00, + VERTICAL_DIRECTION_UP = 0x08, + VERTICAL_DIRECTION_MIDDLE_UP = 0x10, + VERTICAL_DIRECTION_MIDDLE = 0x18, + VERTICAL_DIRECTION_MIDDLE_DOWN = 0x20, + VERTICAL_DIRECTION_DOWN = 0x28, +}; + class MitsubishiClimate : public climate_ir::ClimateIR { public: - MitsubishiClimate() : climate_ir::ClimateIR(MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) {} + MitsubishiClimate() + : climate_ir::ClimateIR(MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX, 1.0f, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MIDDLE, + climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH, climate::CLIMATE_FAN_QUIET}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL, + climate::CLIMATE_SWING_HORIZONTAL}, + {climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_BOOST, + climate::CLIMATE_PRESET_SLEEP}) {} + + void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } + void set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; } + void set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; } + void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } + + void set_fan_mode(SetFanMode fan_mode) { this->fan_mode_ = fan_mode; } + + void set_horizontal_default(HorizontalDirection horizontal_direction) { + this->default_horizontal_direction_ = horizontal_direction; + } + void set_vertical_default(VerticalDirection vertical_direction) { + this->default_vertical_direction_ = vertical_direction; + } protected: - /// Transmit via IR the state of this climate controller. + // Transmit via IR the state of this climate controller. void transmit_state() override; + // Handle received IR Buffer + bool on_receive(remote_base::RemoteReceiveData data) override; + bool parse_state_frame_(const uint8_t frame[]); + + SetFanMode fan_mode_; + + HorizontalDirection default_horizontal_direction_; + VerticalDirection default_vertical_direction_; + + climate::ClimateTraits traits() override; }; } // namespace mitsubishi diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp index c90890c88f..359c6e2f50 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp @@ -13,10 +13,10 @@ void ModbusTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Modbus Controller Te void ModbusTextSensor::parse_and_publish(const std::vector &data) { std::ostringstream output; - uint8_t max_items = this->response_bytes; + uint8_t items_left = this->response_bytes; uint8_t index = this->offset; char buffer[4]; - while ((max_items != 0) && index < data.size()) { + while ((items_left > 0) && index < data.size()) { uint8_t b = data[index]; switch (this->encode_) { case RawEncoding::HEXBYTES: @@ -33,7 +33,7 @@ void ModbusTextSensor::parse_and_publish(const std::vector &data) { output << (char) b; break; } - + items_left--; index++; } diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index baf32097fa..e442eb9146 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -10,6 +10,8 @@ from esphome.const import ( CONF_BIRTH_MESSAGE, CONF_BROKER, CONF_CERTIFICATE_AUTHORITY, + CONF_CLIENT_CERTIFICATE, + CONF_CLIENT_CERTIFICATE_KEY, CONF_CLIENT_ID, CONF_COMMAND_TOPIC, CONF_COMMAND_RETAIN, @@ -111,6 +113,7 @@ MQTTSensorComponent = mqtt_ns.class_("MQTTSensorComponent", MQTTComponent) MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent) MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) +MQTTDatetimeComponent = mqtt_ns.class_("MQTTDatetimeComponent", MQTTComponent) MQTTTextComponent = mqtt_ns.class_("MQTTTextComponent", MQTTComponent) MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent) @@ -199,6 +202,12 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All( cv.string, cv.only_with_esp_idf ), + cv.Inclusive(CONF_CLIENT_CERTIFICATE, "cert-key-pair"): cv.All( + cv.string, cv.only_on_esp32 + ), + cv.Inclusive(CONF_CLIENT_CERTIFICATE_KEY, "cert-key-pair"): cv.All( + cv.string, cv.only_on_esp32 + ), cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All( cv.boolean, cv.only_with_esp_idf ), @@ -378,6 +387,9 @@ async def to_code(config): if CONF_CERTIFICATE_AUTHORITY in config: cg.add(var.set_ca_certificate(config[CONF_CERTIFICATE_AUTHORITY])) cg.add(var.set_skip_cert_cn_check(config[CONF_SKIP_CERT_CN_CHECK])) + if CONF_CLIENT_CERTIFICATE in config: + cg.add(var.set_cl_certificate(config[CONF_CLIENT_CERTIFICATE])) + cg.add(var.set_cl_key(config[CONF_CLIENT_CERTIFICATE_KEY])) # prevent error -0x428e # See https://github.com/espressif/esp-idf/issues/139 @@ -478,6 +490,8 @@ def get_default_topic_for(data, component_type, name, suffix): async def register_mqtt_component(var, config): await cg.register_component(var, {}) + if CONF_QOS in config: + cg.add(var.set_qos(config[CONF_QOS])) if CONF_RETAIN in config: cg.add(var.set_retain(config[CONF_RETAIN])) if not config.get(CONF_DISCOVERY, True): diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index 2d4e6802f2..9c2e487ae7 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -45,6 +45,11 @@ bool MQTTBackendESP32::initialize_() { mqtt_cfg_.cert_pem = ca_certificate_.value().c_str(); mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_; mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_SSL; + + if (this->cl_certificate_.has_value() && this->cl_key_.has_value()) { + mqtt_cfg_.client_cert_pem = this->cl_certificate_.value().c_str(); + mqtt_cfg_.client_key_pem = this->cl_key_.value().c_str(); + } } else { mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP; } @@ -79,6 +84,11 @@ bool MQTTBackendESP32::initialize_() { mqtt_cfg_.broker.verification.certificate = ca_certificate_.value().c_str(); mqtt_cfg_.broker.verification.skip_cert_common_name_check = skip_cert_cn_check_; mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_SSL; + + if (this->cl_certificate_.has_value() && this->cl_key_.has_value()) { + mqtt_cfg_.credentials.authentication.certificate = this->cl_certificate_.value().c_str(); + mqtt_cfg_.credentials.authentication.key = this->cl_key_.value().c_str(); + } } else { mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_TCP; } diff --git a/esphome/components/mqtt/mqtt_backend_esp32.h b/esphome/components/mqtt/mqtt_backend_esp32.h index a4ee96ca59..b1f672da10 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.h +++ b/esphome/components/mqtt/mqtt_backend_esp32.h @@ -124,6 +124,8 @@ class MQTTBackendESP32 final : public MQTTBackend { void loop() final; void set_ca_certificate(const std::string &cert) { ca_certificate_ = cert; } + void set_cl_certificate(const std::string &cert) { cl_certificate_ = cert; } + void set_cl_key(const std::string &key) { cl_key_ = key; } void set_skip_cert_cn_check(bool skip_check) { skip_cert_cn_check_ = skip_check; } protected: @@ -154,6 +156,8 @@ class MQTTBackendESP32 final : public MQTTBackend { uint16_t keep_alive_; bool clean_session_; optional ca_certificate_; + optional cl_certificate_; + optional cl_key_; bool skip_cert_cn_check_{false}; // callbacks diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 79e6989a8f..6d12e88391 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -25,7 +25,7 @@ void MQTTBinarySensorComponent::dump_config() { MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor *binary_sensor) : binary_sensor_(binary_sensor) { if (this->binary_sensor_->is_status_binary_sensor()) { - this->set_custom_state_topic(mqtt::global_mqtt_client->get_availability().topic); + this->set_custom_state_topic(mqtt::global_mqtt_client->get_availability().topic.c_str()); } } diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 923762aea4..abcbb414d9 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -91,8 +91,13 @@ void MQTTClientComponent::send_device_info_() { this->publish_json( topic, [](JsonObject root) { - auto ip = network::get_ip_address(); - root["ip"] = ip.str(); + uint8_t index = 0; + for (auto &ip : network::get_ip_addresses()) { + if (ip.is_set()) { + root["ip" + (index == 0 ? "" : esphome::to_string(index))] = ip.str(); + index++; + } + } root["name"] = App.get_name(); #ifdef USE_API root["port"] = api::global_api_server->get_port(); @@ -159,14 +164,13 @@ void MQTTClientComponent::start_dnslookup_() { this->dns_resolve_error_ = false; this->dns_resolved_ = false; ip_addr_t addr; -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if USE_NETWORK_IPV6 + err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, + MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV6_IPV4); +#else err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV4); -#endif -#ifdef USE_ESP8266 - err_t err = dns_gethostbyname(this->credentials_.address.c_str(), &addr, - esphome::mqtt::MQTTClientComponent::dns_found_callback, this); -#endif +#endif /* USE_NETWORK_IPV6 */ switch (err) { case ERR_OK: { // Got IP immediately @@ -183,11 +187,7 @@ void MQTTClientComponent::start_dnslookup_() { default: case ERR_ARG: { // error -#if defined(USE_ESP8266) - ESP_LOGW(TAG, "Error resolving MQTT broker IP address: %ld", err); -#else ESP_LOGW(TAG, "Error resolving MQTT broker IP address: %d", err); -#endif break; } } @@ -470,8 +470,8 @@ bool MQTTClientComponent::publish(const MQTTMessage &message) { if (!logging_topic) { if (ret) { - ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", message.topic.c_str(), message.payload.c_str(), - message.retain); + ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d qos=%d)", message.topic.c_str(), message.payload.c_str(), + message.retain, message.qos); } else { ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", message.topic.c_str(), message.payload.length()); diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index bcb44ab4c2..454316aa87 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -146,6 +146,8 @@ class MQTTClientComponent : public Component { #endif #ifdef USE_ESP32 void set_ca_certificate(const char *cert) { this->mqtt_backend_.set_ca_certificate(cert); } + void set_cl_certificate(const char *cert) { this->mqtt_backend_.set_cl_certificate(cert); } + void set_cl_key(const char *key) { this->mqtt_backend_.set_cl_key(key); } void set_skip_cert_cn_check(bool skip_check) { this->mqtt_backend_.set_skip_cert_cn_check(skip_check); } #endif const Availability &get_availability(); diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index af4d6f13a5..bb46ce732d 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -14,6 +14,8 @@ namespace mqtt { static const char *const TAG = "mqtt.component"; +void MQTTComponent::set_qos(uint8_t qos) { this->qos_ = qos; } + void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; } std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const { @@ -34,26 +36,26 @@ std::string MQTTComponent::get_default_topic_for_(const std::string &suffix) con std::string MQTTComponent::get_state_topic_() const { if (this->has_custom_state_topic_) - return this->custom_state_topic_; + return this->custom_state_topic_.str(); return this->get_default_topic_for_("state"); } std::string MQTTComponent::get_command_topic_() const { if (this->has_custom_command_topic_) - return this->custom_command_topic_; + return this->custom_command_topic_.str(); return this->get_default_topic_for_("command"); } bool MQTTComponent::publish(const std::string &topic, const std::string &payload) { if (topic.empty()) return false; - return global_mqtt_client->publish(topic, payload, 0, this->retain_); + return global_mqtt_client->publish(topic, payload, this->qos_, this->retain_); } bool MQTTComponent::publish_json(const std::string &topic, const json::json_build_t &f) { if (topic.empty()) return false; - return global_mqtt_client->publish_json(topic, f, 0, this->retain_); + return global_mqtt_client->publish_json(topic, f, this->qos_, this->retain_); } bool MQTTComponent::send_discovery_() { @@ -61,7 +63,7 @@ bool MQTTComponent::send_discovery_() { if (discovery_info.clean) { ESP_LOGV(TAG, "'%s': Cleaning discovery...", this->friendly_name().c_str()); - return global_mqtt_client->publish(this->get_discovery_topic_(discovery_info), "", 0, 0, true); + return global_mqtt_client->publish(this->get_discovery_topic_(discovery_info), "", 0, this->qos_, true); } ESP_LOGV(TAG, "'%s': Sending discovery...", this->friendly_name().c_str()); @@ -155,9 +157,11 @@ bool MQTTComponent::send_discovery_() { device_info[MQTT_DEVICE_MANUFACTURER] = "espressif"; device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area; }, - 0, discovery_info.retain); + this->qos_, discovery_info.retain); } +uint8_t MQTTComponent::get_qos() const { return this->qos_; } + bool MQTTComponent::get_retain() const { return this->retain_; } bool MQTTComponent::is_discovery_enabled() const { @@ -180,12 +184,12 @@ MQTTComponent::MQTTComponent() = default; float MQTTComponent::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } void MQTTComponent::disable_discovery() { this->discovery_enabled_ = false; } -void MQTTComponent::set_custom_state_topic(const std::string &custom_state_topic) { - this->custom_state_topic_ = custom_state_topic; +void MQTTComponent::set_custom_state_topic(const char *custom_state_topic) { + this->custom_state_topic_ = StringRef(custom_state_topic); this->has_custom_state_topic_ = true; } -void MQTTComponent::set_custom_command_topic(const std::string &custom_command_topic) { - this->custom_command_topic_ = custom_command_topic; +void MQTTComponent::set_custom_command_topic(const char *custom_command_topic) { + this->custom_command_topic_ = StringRef(custom_command_topic); this->has_custom_command_topic_ = true; } void MQTTComponent::set_command_retain(bool command_retain) { this->command_retain_ = command_retain; } diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index aacfe8891f..147840d11f 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -8,6 +8,7 @@ #include "esphome/core/component.h" #include "esphome/core/entity_base.h" +#include "esphome/core/string_ref.h" #include "mqtt_client.h" namespace esphome { @@ -76,6 +77,10 @@ class MQTTComponent : public Component { virtual bool is_internal(); + /// Set QOS for state messages. + void set_qos(uint8_t qos); + uint8_t get_qos() const; + /// Set whether state message should be retained. void set_retain(bool retain); bool get_retain() const; @@ -88,9 +93,9 @@ class MQTTComponent : public Component { virtual std::string component_type() const = 0; /// Set a custom state topic. Set to "" for default behavior. - void set_custom_state_topic(const std::string &custom_state_topic); + void set_custom_state_topic(const char *custom_state_topic); /// Set a custom command topic. Set to "" for default behavior. - void set_custom_command_topic(const std::string &custom_command_topic); + void set_custom_command_topic(const char *custom_command_topic); /// Set whether command message should be retained. void set_command_retain(bool command_retain); @@ -188,15 +193,18 @@ class MQTTComponent : public Component { /// Generate the Home Assistant MQTT discovery object id by automatically transforming the friendly name. std::string get_default_object_id_() const; - std::string custom_state_topic_{}; - std::string custom_command_topic_{}; + StringRef custom_state_topic_{}; + StringRef custom_command_topic_{}; + + std::unique_ptr availability_; + bool has_custom_state_topic_{false}; bool has_custom_command_topic_{false}; bool command_retain_{false}; bool retain_{true}; + uint8_t qos_{0}; bool discovery_enabled_{true}; - std::unique_ptr availability_; bool resend_state_{false}; }; diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp new file mode 100644 index 0000000000..088a4788ed --- /dev/null +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -0,0 +1,68 @@ +#include "mqtt_date.h" + +#include +#include "esphome/core/log.h" + +#include "mqtt_const.h" + +#ifdef USE_MQTT +#ifdef USE_DATETIME_DATE + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.datetime"; + +using namespace esphome::datetime; + +MQTTDateComponent::MQTTDateComponent(DateEntity *date) : date_(date) {} + +void MQTTDateComponent::setup() { + this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { + auto call = this->date_->make_call(); + if (root.containsKey("year")) { + call.set_year(root["year"]); + } + if (root.containsKey("month")) { + call.set_month(root["month"]); + } + if (root.containsKey("day")) { + call.set_day(root["day"]); + } + call.perform(); + }); + this->date_->add_on_state_callback( + [this]() { this->publish_state(this->date_->year, this->date_->month, this->date_->day); }); +} + +void MQTTDateComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT Date '%s':", this->date_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, true) +} + +std::string MQTTDateComponent::component_type() const { return "date"; } +const EntityBase *MQTTDateComponent::get_entity() const { return this->date_; } + +void MQTTDateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // Nothing extra to add here +} +bool MQTTDateComponent::send_initial_state() { + if (this->date_->has_state()) { + return this->publish_state(this->date_->year, this->date_->month, this->date_->day); + } else { + return true; + } +} +bool MQTTDateComponent::publish_state(uint16_t year, uint8_t month, uint8_t day) { + return this->publish_json(this->get_state_topic_(), [year, month, day](JsonObject root) { + root["year"] = year; + root["month"] = month; + root["day"] = day; + }); +} + +} // namespace mqtt +} // namespace esphome + +#endif // USE_DATETIME_DATE +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_date.h b/esphome/components/mqtt/mqtt_date.h new file mode 100644 index 0000000000..2776893d32 --- /dev/null +++ b/esphome/components/mqtt/mqtt_date.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT +#ifdef USE_DATETIME_DATE + +#include "esphome/components/datetime/date_entity.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTDateComponent : public mqtt::MQTTComponent { + public: + /** Construct this MQTTDatetimeComponent instance with the provided friendly_name and datetime + * + * @param datetime The datetime component. + */ + explicit MQTTDateComponent(datetime::DateEntity *date); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + /// Override setup. + void setup() override; + void dump_config() override; + + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; + + bool send_initial_state() override; + + bool publish_state(uint16_t year, uint8_t month, uint8_t day); + + protected: + std::string component_type() const override; + const EntityBase *get_entity() const override; + + datetime::DateEntity *date_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif // USE_DATETIME_DATE +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index d0d3174bfe..b0754bc8b3 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -1,6 +1,8 @@ #include "mqtt_text_sensor.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_TEXT_SENSOR @@ -13,6 +15,8 @@ using namespace esphome::text_sensor; MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {} void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + if (!this->sensor_->get_device_class().empty()) + root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); config.command_topic = false; } void MQTTTextSensor::setup() { diff --git a/esphome/components/ms8607/__init__.py b/esphome/components/ms8607/__init__.py new file mode 100644 index 0000000000..e1cd49ec7b --- /dev/null +++ b/esphome/components/ms8607/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@e28eta"] diff --git a/esphome/components/ms8607/ms8607.cpp b/esphome/components/ms8607/ms8607.cpp new file mode 100644 index 0000000000..4ad6ac336d --- /dev/null +++ b/esphome/components/ms8607/ms8607.cpp @@ -0,0 +1,444 @@ +#include "ms8607.h" + +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ms8607 { + +/// TAG used for logging calls +static const char *const TAG = "ms8607"; + +/// Reset the Pressure/Temperature sensor +static const uint8_t MS8607_PT_CMD_RESET = 0x1E; + +/// Beginning of PROM register addresses. Same for both i2c addresses. Each address has 16 bits of data, and +/// PROM addresses step by two, so the LSB is always 0 +static const uint8_t MS8607_PROM_START = 0xA0; +/// Last PROM register address. +static const uint8_t MS8607_PROM_END = 0xAE; +/// Number of PROM registers. +static const uint8_t MS8607_PROM_COUNT = (MS8607_PROM_END - MS8607_PROM_START) >> 1; + +/// Reset the Humidity sensor +static const uint8_t MS8607_CMD_H_RESET = 0xFE; +/// Read relative humidity, without holding i2c master +static const uint8_t MS8607_CMD_H_MEASURE_NO_HOLD = 0xF5; +/// Temperature correction coefficient for Relative Humidity from datasheet +static const float MS8607_H_TEMP_COEFFICIENT = -0.18; + +/// Read the converted analog value, either D1 (pressure) or D2 (temperature) +static const uint8_t MS8607_CMD_ADC_READ = 0x00; + +// TODO: allow OSR to be turned down for speed and/or lower power consumption via configuration. +// ms8607 supports 6 different settings + +/// Request conversion of analog D1 (pressure) with OSR=8192 (highest oversampling ratio). Takes maximum of 17.2ms +static const uint8_t MS8607_CMD_CONV_D1_OSR_8K = 0x4A; +/// Request conversion of analog D2 (temperature) with OSR=8192 (highest oversampling ratio). Takes maximum of 17.2ms +static const uint8_t MS8607_CMD_CONV_D2_OSR_8K = 0x5A; + +enum class MS8607Component::ErrorCode { + /// Component hasn't failed (yet?) + NONE = 0, + /// Both the Pressure/Temperature address and the Humidity address failed to reset + PTH_RESET_FAILED = 1, + /// Asking the Pressure/Temperature sensor to reset failed + PT_RESET_FAILED = 2, + /// Asking the Humidity sensor to reset failed + H_RESET_FAILED = 3, + /// Reading the PROM calibration values failed + PROM_READ_FAILED = 4, + /// The PROM calibration values failed the CRC check + PROM_CRC_FAILED = 5, +}; + +enum class MS8607Component::SetupStatus { + /// This component has not successfully reset the PT & H devices + NEEDS_RESET, + /// Reset commands succeeded, need to wait >= 15ms to read PROM + NEEDS_PROM_READ, + /// Successfully read PROM and ready to update sensors + SUCCESSFUL, +}; + +static uint8_t crc4(uint16_t *buffer, size_t length); +static uint8_t hsensor_crc_check(uint16_t value); + +void MS8607Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MS8607..."); + this->error_code_ = ErrorCode::NONE; + this->setup_status_ = SetupStatus::NEEDS_RESET; + + // I do not know why the device sometimes NACKs the reset command, but + // try 3 times in case it's a transitory issue on this boot + this->set_retry( + "reset", 5, 3, + [this](const uint8_t remaining_setup_attempts) { + ESP_LOGD(TAG, "Resetting both I2C addresses: 0x%02X, 0x%02X", this->address_, + this->humidity_device_->get_address()); + // I believe sending the reset command to both addresses is preferable to + // skipping humidity if PT fails for some reason. + // However, only consider the reset successful if they both ACK + bool const pt_successful = this->write_bytes(MS8607_PT_CMD_RESET, nullptr, 0); + bool const h_successful = this->humidity_device_->write_bytes(MS8607_CMD_H_RESET, nullptr, 0); + + if (!(pt_successful && h_successful)) { + ESP_LOGE(TAG, "Resetting I2C devices failed"); + if (!pt_successful && !h_successful) { + this->error_code_ = ErrorCode::PTH_RESET_FAILED; + } else if (!pt_successful) { + this->error_code_ = ErrorCode::PT_RESET_FAILED; + } else { + this->error_code_ = ErrorCode::H_RESET_FAILED; + } + + if (remaining_setup_attempts > 0) { + this->status_set_error(); + } else { + this->mark_failed(); + } + return RetryResult::RETRY; + } + + this->setup_status_ = SetupStatus::NEEDS_PROM_READ; + this->error_code_ = ErrorCode::NONE; + this->status_clear_error(); + + // 15ms delay matches datasheet, Adafruit_MS8607 & SparkFun_PHT_MS8607_Arduino_Library + this->set_timeout("prom-read", 15, [this]() { + if (this->read_calibration_values_from_prom_()) { + this->setup_status_ = SetupStatus::SUCCESSFUL; + this->status_clear_error(); + } else { + this->mark_failed(); + return; + } + }); + + return RetryResult::DONE; + }, + 5.0f); // executes at now, +5ms, +25ms +} + +void MS8607Component::update() { + if (this->setup_status_ != SetupStatus::SUCCESSFUL) { + // setup is still occurring, either because reset had to retry or due to the 15ms + // delay needed between reset & reading the PROM values + return; + } + + // Updating happens async and sequentially. + // Temperature, then pressure, then humidity + this->request_read_temperature_(); +} + +void MS8607Component::dump_config() { + ESP_LOGCONFIG(TAG, "MS8607:"); + LOG_I2C_DEVICE(this); + // LOG_I2C_DEVICE doesn't work for humidity, the `address_` is protected. Log using get_address() + ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->humidity_device_->get_address()); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MS8607 failed."); + switch (this->error_code_) { + case ErrorCode::PT_RESET_FAILED: + ESP_LOGE(TAG, "Temperature/Pressure RESET failed"); + break; + case ErrorCode::H_RESET_FAILED: + ESP_LOGE(TAG, "Humidity RESET failed"); + break; + case ErrorCode::PTH_RESET_FAILED: + ESP_LOGE(TAG, "Temperature/Pressure && Humidity RESET failed"); + break; + case ErrorCode::PROM_READ_FAILED: + ESP_LOGE(TAG, "Reading PROM failed"); + break; + case ErrorCode::PROM_CRC_FAILED: + ESP_LOGE(TAG, "PROM values failed CRC"); + break; + case ErrorCode::NONE: + default: + ESP_LOGE(TAG, "Error reason unknown %u", static_cast(this->error_code_)); + break; + } + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); +} + +bool MS8607Component::read_calibration_values_from_prom_() { + ESP_LOGD(TAG, "Reading calibration values from PROM"); + + uint16_t buffer[MS8607_PROM_COUNT]; + bool successful = true; + + for (uint8_t idx = 0; idx < MS8607_PROM_COUNT; ++idx) { + uint8_t const address_to_read = MS8607_PROM_START + (idx * 2); + successful &= this->read_byte_16(address_to_read, &buffer[idx]); + } + + if (!successful) { + ESP_LOGE(TAG, "Reading calibration values from PROM failed"); + this->error_code_ = ErrorCode::PROM_READ_FAILED; + return false; + } + + ESP_LOGD(TAG, "Checking CRC of calibration values from PROM"); + uint8_t const expected_crc = (buffer[0] & 0xF000) >> 12; // first 4 bits + buffer[0] &= 0x0FFF; // strip CRC from buffer, in order to run CRC + uint8_t const actual_crc = crc4(buffer, MS8607_PROM_COUNT); + + if (expected_crc != actual_crc) { + ESP_LOGE(TAG, "Incorrect CRC value. Provided value 0x%01X != calculated value 0x%01X", expected_crc, actual_crc); + this->error_code_ = ErrorCode::PROM_CRC_FAILED; + return false; + } + + this->calibration_values_.pressure_sensitivity = buffer[1]; + this->calibration_values_.pressure_offset = buffer[2]; + this->calibration_values_.pressure_sensitivity_temperature_coefficient = buffer[3]; + this->calibration_values_.pressure_offset_temperature_coefficient = buffer[4]; + this->calibration_values_.reference_temperature = buffer[5]; + this->calibration_values_.temperature_coefficient_of_temperature = buffer[6]; + ESP_LOGD(TAG, "Finished reading calibration values"); + + // Skipping reading Humidity PROM, since it doesn't have anything interesting for us + + return true; +} + +/** + CRC-4 algorithm from datasheet. It operates on a buffer of 16-bit values, one byte at a time, using a 16-bit + value to collect the CRC result into. + + The provided/expected CRC value must already be zeroed out from the buffer. + */ +static uint8_t crc4(uint16_t *buffer, size_t length) { + uint16_t crc_remainder = 0; + + // algorithm to add a byte into the crc + auto apply_crc = [&crc_remainder](uint8_t next) { + crc_remainder ^= next; + for (uint8_t bit = 8; bit > 0; --bit) { + if (crc_remainder & 0x8000) { + crc_remainder = (crc_remainder << 1) ^ 0x3000; + } else { + crc_remainder = (crc_remainder << 1); + } + } + }; + + // add all the bytes + for (size_t idx = 0; idx < length; ++idx) { + for (auto byte : decode_value(buffer[idx])) { + apply_crc(byte); + } + } + // For the MS8607 CRC, add a pair of zeros to shift the last byte from `buffer` through + apply_crc(0); + apply_crc(0); + + return (crc_remainder >> 12) & 0xF; // only the most significant 4 bits +} + +/** + * @brief Calculates CRC value for the provided humidity (+ status bits) value + * + * CRC-8 check comes from other MS8607 libraries on github. I did not find it in the datasheet, + * and it differs from the crc8 implementation that's already part of esphome. + * + * @param value two byte humidity sensor value read from i2c + * @return uint8_t computed crc value + */ +static uint8_t hsensor_crc_check(uint16_t value) { + uint32_t polynom = 0x988000; // x^8 + x^5 + x^4 + 1 + uint32_t msb = 0x800000; + uint32_t mask = 0xFF8000; + uint32_t result = (uint32_t) value << 8; // Pad with zeros as specified in spec + + while (msb != 0x80) { + // Check if msb of current value is 1 and apply XOR mask + if (result & msb) { + result = ((result ^ polynom) & mask) | (result & ~mask); + } + + // Shift by one + msb >>= 1; + mask >>= 1; + polynom >>= 1; + } + return result & 0xFF; +} + +void MS8607Component::request_read_temperature_() { + // Tell MS8607 to start ADC conversion of temperature sensor + if (!this->write_bytes(MS8607_CMD_CONV_D2_OSR_8K, nullptr, 0)) { + this->status_set_warning(); + return; + } + + auto f = std::bind(&MS8607Component::read_temperature_, this); + // datasheet says 17.2ms max conversion time at OSR 8192 + this->set_timeout("temperature", 20, f); +} + +void MS8607Component::read_temperature_() { + uint8_t bytes[3]; // 24 bits + if (!this->read_bytes(MS8607_CMD_ADC_READ, bytes, 3)) { + this->status_set_warning(); + return; + } + + const uint32_t d2_raw_temperature = encode_uint32(0, bytes[0], bytes[1], bytes[2]); + this->request_read_pressure_(d2_raw_temperature); +} + +void MS8607Component::request_read_pressure_(uint32_t d2_raw_temperature) { + if (!this->write_bytes(MS8607_CMD_CONV_D1_OSR_8K, nullptr, 0)) { + this->status_set_warning(); + return; + } + + auto f = std::bind(&MS8607Component::read_pressure_, this, d2_raw_temperature); + // datasheet says 17.2ms max conversion time at OSR 8192 + this->set_timeout("pressure", 20, f); +} + +void MS8607Component::read_pressure_(uint32_t d2_raw_temperature) { + uint8_t bytes[3]; // 24 bits + if (!this->read_bytes(MS8607_CMD_ADC_READ, bytes, 3)) { + this->status_set_warning(); + return; + } + const uint32_t d1_raw_pressure = encode_uint32(0, bytes[0], bytes[1], bytes[2]); + this->calculate_values_(d2_raw_temperature, d1_raw_pressure); +} + +void MS8607Component::request_read_humidity_(float temperature_float) { + if (!this->humidity_device_->write_bytes(MS8607_CMD_H_MEASURE_NO_HOLD, nullptr, 0)) { + ESP_LOGW(TAG, "Request to measure humidity failed"); + this->status_set_warning(); + return; + } + + auto f = std::bind(&MS8607Component::read_humidity_, this, temperature_float); + // datasheet says 15.89ms max conversion time at OSR 8192 + this->set_timeout("humidity", 20, f); +} + +void MS8607Component::read_humidity_(float temperature_float) { + uint8_t bytes[3]; + if (!this->humidity_device_->read_bytes_raw(bytes, 3)) { + ESP_LOGW(TAG, "Failed to read the measured humidity value"); + this->status_set_warning(); + return; + } + + // "the measurement is stored into 14 bits. The two remaining LSBs are used for transmitting status information. + // Bit1 of the two LSBS must be set to '1'. Bit0 is currently not assigned" + uint16_t humidity = encode_uint16(bytes[0], bytes[1]); + uint8_t const expected_crc = bytes[2]; + uint8_t const actual_crc = hsensor_crc_check(humidity); + if (expected_crc != actual_crc) { + ESP_LOGE(TAG, "Incorrect Humidity CRC value. Provided value 0x%01X != calculated value 0x%01X", expected_crc, + actual_crc); + this->status_set_warning(); + return; + } + if (!(humidity & 0x2)) { + // data sheet says Bit1 should always set, but nothing about what happens if it isn't + ESP_LOGE(TAG, "Humidity status bit was not set to 1?"); + } + humidity &= ~(0b11); // strip status & unassigned bits from data + + // map 16 bit humidity value into range [-6%, 118%] + float const humidity_partial = double(humidity) / (1 << 16); + float const humidity_percentage = lerp(humidity_partial, -6.0, 118.0); + float const compensated_humidity_percentage = + humidity_percentage + (20 - temperature_float) * MS8607_H_TEMP_COEFFICIENT; + ESP_LOGD(TAG, "Compensated for temperature, humidity=%.2f%%", compensated_humidity_percentage); + + if (this->humidity_sensor_ != nullptr) { + this->humidity_sensor_->publish_state(compensated_humidity_percentage); + } + this->status_clear_warning(); +} + +void MS8607Component::calculate_values_(uint32_t d2_raw_temperature, uint32_t d1_raw_pressure) { + // Perform the first order pressure/temperature calculation + + // d_t: "difference between actual and reference temperature" = D2 - [C5] * 2**8 + const int32_t d_t = int32_t(d2_raw_temperature) - (int32_t(this->calibration_values_.reference_temperature) << 8); + // actual temperature as hundredths of degree celsius in range [-4000, 8500] + // 2000 + d_t * [C6] / (2**23) + int32_t temperature = + 2000 + ((int64_t(d_t) * this->calibration_values_.temperature_coefficient_of_temperature) >> 23); + + // offset at actual temperature. [C2] * (2**17) + (d_t * [C4] / (2**6)) + int64_t pressure_offset = (int64_t(this->calibration_values_.pressure_offset) << 17) + + ((int64_t(d_t) * this->calibration_values_.pressure_offset_temperature_coefficient) >> 6); + // sensitivity at actual temperature. [C1] * (2**16) + ([C3] * d_t) / (2**7) + int64_t pressure_sensitivity = + (int64_t(this->calibration_values_.pressure_sensitivity) << 16) + + ((int64_t(d_t) * this->calibration_values_.pressure_sensitivity_temperature_coefficient) >> 7); + + // Perform the second order compensation, for non-linearity over temperature range + const int64_t d_t_squared = int64_t(d_t) * d_t; + int64_t temperature_2 = 0; + int32_t pressure_offset_2 = 0; + int32_t pressure_sensitivity_2 = 0; + if (temperature < 2000) { + // (TEMP - 2000)**2 / 2**4 + const int32_t low_temperature_adjustment = (temperature - 2000) * (temperature - 2000) >> 4; + + // T2 = 3 * (d_t**2) / 2**33 + temperature_2 = (3 * d_t_squared) >> 33; + // OFF2 = 61 * (TEMP-2000)**2 / 2**4 + pressure_offset_2 = 61 * low_temperature_adjustment; + // SENS2 = 29 * (TEMP-2000)**2 / 2**4 + pressure_sensitivity_2 = 29 * low_temperature_adjustment; + + if (temperature < -1500) { + // (TEMP+1500)**2 + const int32_t very_low_temperature_adjustment = (temperature + 1500) * (temperature + 1500); + + // OFF2 = OFF2 + 17 * (TEMP+1500)**2 + pressure_offset_2 += 17 * very_low_temperature_adjustment; + // SENS2 = SENS2 + 9 * (TEMP+1500)**2 + pressure_sensitivity_2 += 9 * very_low_temperature_adjustment; + } + } else { + // T2 = 5 * (d_t**2) / 2**38 + temperature_2 = (5 * d_t_squared) >> 38; + } + + temperature -= temperature_2; + pressure_offset -= pressure_offset_2; + pressure_sensitivity -= pressure_sensitivity_2; + + // Temperature compensated pressure. [1000, 120000] => [10.00 mbar, 1200.00 mbar] + const int32_t pressure = (((d1_raw_pressure * pressure_sensitivity) >> 21) - pressure_offset) >> 15; + + const float temperature_float = temperature / 100.0f; + const float pressure_float = pressure / 100.0f; + ESP_LOGD(TAG, "Temperature=%0.2f°C, Pressure=%0.2fhPa", temperature_float, pressure_float); + + if (this->temperature_sensor_ != nullptr) { + this->temperature_sensor_->publish_state(temperature_float); + } + if (this->pressure_sensor_ != nullptr) { + this->pressure_sensor_->publish_state(pressure_float); // hPa aka mbar + } + this->status_clear_warning(); + + if (this->humidity_sensor_ != nullptr) { + // now that we have temperature (to compensate the humidity with), kick off that read + this->request_read_humidity_(temperature_float); + } +} + +} // namespace ms8607 +} // namespace esphome diff --git a/esphome/components/ms8607/ms8607.h b/esphome/components/ms8607/ms8607.h new file mode 100644 index 0000000000..0bee7e97b7 --- /dev/null +++ b/esphome/components/ms8607/ms8607.h @@ -0,0 +1,109 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace ms8607 { + +/** + Class for I2CDevice used to communicate with the Humidity sensor + on the chip. See MS8607Component instead + */ +class MS8607HumidityDevice : public i2c::I2CDevice { + public: + uint8_t get_address() { return address_; } +}; + +/** + Temperature, pressure, and humidity sensor. + + By default, the MS8607 measures sensors at the highest resolution. + A potential enhancement would be to expose the resolution as a configurable + setting. A lower resolution speeds up ADC conversion time & uses less power. + + Datasheet: + https://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FMS8607-02BA01%7FB3%7Fpdf%7FEnglish%7FENG_DS_MS8607-02BA01_B3.pdf%7FCAT-BLPS0018 + + Other implementations: + - https://github.com/TEConnectivity/MS8607_Generic_C_Driver + - https://github.com/adafruit/Adafruit_MS8607 + - https://github.com/sparkfun/SparkFun_PHT_MS8607_Arduino_Library + */ +class MS8607Component : public PollingComponent, public i2c::I2CDevice { + public: + virtual ~MS8607Component() = default; + void setup() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; }; + + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } + void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } + void set_humidity_device(MS8607HumidityDevice *humidity_device) { humidity_device_ = humidity_device; } + + protected: + /** + Read and store the Pressure & Temperature calibration settings from the PROM. + Intended to be called during setup(), this will set the `failure_reason_` + */ + bool read_calibration_values_from_prom_(); + + /// Start async temperature read + void request_read_temperature_(); + /// Process async temperature read + void read_temperature_(); + /// start async pressure read + void request_read_pressure_(uint32_t raw_temperature); + /// process async pressure read + void read_pressure_(uint32_t raw_temperature); + /// start async humidity read + void request_read_humidity_(float temperature_float); + /// process async humidity read + void read_humidity_(float temperature_float); + /// use raw temperature & pressure to calculate & publish values + void calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure); + + sensor::Sensor *temperature_sensor_; + sensor::Sensor *pressure_sensor_; + sensor::Sensor *humidity_sensor_; + + /** I2CDevice object to communicate with secondary I2C address for the humidity sensor + * + * The MS8607 only has one set of I2C pins, despite using two different addresses. + * + * Default address for humidity is 0x40 + */ + MS8607HumidityDevice *humidity_device_; + + /// This device's pressure & temperature calibration values, read from PROM + struct CalibrationValues { + /// Pressure sensitivity | SENS-T1. [C1] + uint16_t pressure_sensitivity; + /// Temperature coefficient of pressure sensitivity | TCS. [C3] + uint16_t pressure_sensitivity_temperature_coefficient; + /// Pressure offset | OFF-T1. [C2] + uint16_t pressure_offset; + /// Temperature coefficient of pressure offset | TCO. [C4] + uint16_t pressure_offset_temperature_coefficient; + /// Reference temperature | T-REF. [C5] + uint16_t reference_temperature; + /// Temperature coefficient of the temperature | TEMPSENS. [C6] + uint16_t temperature_coefficient_of_temperature; + } calibration_values_; + + /// Possible failure reasons of this component + enum class ErrorCode; + /// Keep track of the reason why this component failed, to augment the dumped config + ErrorCode error_code_; + + /// Current progress through required component setup + enum class SetupStatus; + /// Current step in the multi-step & possibly delayed setup() process + SetupStatus setup_status_; +}; + +} // namespace ms8607 +} // namespace esphome diff --git a/esphome/components/ms8607/sensor.py b/esphome/components/ms8607/sensor.py new file mode 100644 index 0000000000..1113e14af2 --- /dev/null +++ b/esphome/components/ms8607/sensor.py @@ -0,0 +1,83 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, + UNIT_PERCENT, +) + +DEPENDENCIES = ["i2c"] + +ms8607_ns = cg.esphome_ns.namespace("ms8607") +MS8607Component = ms8607_ns.class_( + "MS8607Component", cg.PollingComponent, i2c.I2CDevice +) + +CONF_HUMIDITY_I2C_ID = "humidity_i2c_id" +MS8607HumidityDevice = ms8607_ns.class_("MS8607HumidityDevice", i2c.I2CDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MS8607Component), + cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, # Resolution: 0.01 + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Required(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=2, # Resolution: 0.016 + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Required(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=2, # Resolution: 0.04 + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.GenerateID(CONF_HUMIDITY_I2C_ID): cv.declare_id( + MS8607HumidityDevice + ), + } + ) + .extend(i2c.i2c_device_schema(0x40)), # default address for humidity + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x76)) # default address for temp/pressure +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + + if pressure_config := config.get(CONF_PRESSURE): + sens = await sensor.new_sensor(pressure_config) + cg.add(var.set_pressure_sensor(sens)) + + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity_sensor(sens)) + humidity_device = cg.new_Pvariable(humidity_config[CONF_HUMIDITY_I2C_ID]) + await i2c.register_i2c_device(humidity_device, humidity_config) + cg.add(var.set_humidity_device(humidity_device)) diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index dd1353f86f..36144ff0a4 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -5,6 +5,10 @@ from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.const import ( CONF_ENABLE_IPV6, + CONF_MIN_IPV6_ADDR_COUNT, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RP2040, ) CODEOWNERS = ["@esphome/core"] @@ -15,23 +19,30 @@ IPAddress = network_ns.class_("IPAddress") CONFIG_SCHEMA = cv.Schema( { - cv.Optional(CONF_ENABLE_IPV6, default=False): cv.boolean, + cv.SplitDefault(CONF_ENABLE_IPV6): cv.All( + cv.boolean, cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]) + ), + cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int, } ) async def to_code(config): - cg.add_define("ENABLE_IPV6", config[CONF_ENABLE_IPV6]) - if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6]) - add_idf_sdkconfig_option( - "CONFIG_LWIP_IPV6_AUTOCONFIG", config[CONF_ENABLE_IPV6] + if CONF_ENABLE_IPV6 in config: + cg.add_define("USE_NETWORK_IPV6", config[CONF_ENABLE_IPV6]) + cg.add_define( + "USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT] ) - else: - if config[CONF_ENABLE_IPV6]: - cg.add_build_flag("-DCONFIG_LWIP_IPV6") - cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG") - if CORE.is_rp2040: - cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6") - if CORE.is_esp8266: - cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY") + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6]) + add_idf_sdkconfig_option( + "CONFIG_LWIP_IPV6_AUTOCONFIG", config[CONF_ENABLE_IPV6] + ) + else: + if config[CONF_ENABLE_IPV6]: + cg.add_build_flag("-DCONFIG_LWIP_IPV6") + cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG") + if CORE.is_rp2040: + cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6") + if CORE.is_esp8266: + cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY") diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index 709524c9d1..30a426e458 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -4,6 +4,7 @@ #include #include #include "esphome/core/macros.h" +#include "esphome/core/helpers.h" #if defined(USE_ESP_IDF) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0) #include @@ -14,6 +15,13 @@ #include #endif /* USE_ADRDUINO */ +#ifdef USE_HOST +#include +using ip_addr_t = in_addr; +using ip4_addr_t = in_addr; +#define ipaddr_aton(x, y) inet_aton((x), (y)) +#endif + #if USE_ESP32_FRAMEWORK_ARDUINO #define arduino_ns Arduino_h #elif USE_LIBRETINY @@ -32,6 +40,14 @@ namespace network { struct IPAddress { public: +#ifdef USE_HOST + IPAddress() { ip_addr_.s_addr = 0; } + IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) { + this->ip_addr_.s_addr = htonl((first << 24) | (second << 16) | (third << 8) | fourth); + } + IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); } + IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; } +#else IPAddress() { ip_addr_set_zero(&ip_addr_); } IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) { IP_ADDR4(&ip_addr_, first, second, third, fourth); @@ -62,6 +78,13 @@ struct IPAddress { } #endif /* LWIP_IPV6 */ IPAddress(esp_ip4_addr_t *other_ip) { memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(esp_ip4_addr_t)); } + IPAddress(esp_ip_addr_t *other_ip) { +#if LWIP_IPV6 + memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip_addr_)); +#else + memcpy((void *) &ip_addr_, (void *) &other_ip->u_addr.ip4, sizeof(ip_addr_)); +#endif + } operator esp_ip_addr_t() const { esp_ip_addr_t tmp; #if LWIP_IPV6 @@ -94,7 +117,7 @@ struct IPAddress { bool is_set() { return !ip_addr_isany(&ip_addr_); } bool is_ip4() { return IP_IS_V4(&ip_addr_); } bool is_ip6() { return IP_IS_V6(&ip_addr_); } - std::string str() const { return ipaddr_ntoa(&ip_addr_); } + std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); } bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); } bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); } IPAddress &operator+=(uint8_t increase) { @@ -107,10 +130,13 @@ struct IPAddress { } return *this; } +#endif protected: ip_addr_t ip_addr_; }; +using IPAddresses = std::array; + } // namespace network } // namespace esphome diff --git a/esphome/components/network/util.cpp b/esphome/components/network/util.cpp index 109c2947ce..445485b644 100644 --- a/esphome/components/network/util.cpp +++ b/esphome/components/network/util.cpp @@ -37,14 +37,14 @@ bool is_disabled() { return false; } -network::IPAddress get_ip_address() { +network::IPAddresses get_ip_addresses() { #ifdef USE_ETHERNET if (ethernet::global_eth_component != nullptr) - return ethernet::global_eth_component->get_ip_address(); + return ethernet::global_eth_component->get_ip_addresses(); #endif #ifdef USE_WIFI if (wifi::global_wifi_component != nullptr) - return wifi::global_wifi_component->get_ip_address(); + return wifi::global_wifi_component->get_ip_addresses(); #endif return {}; } diff --git a/esphome/components/network/util.h b/esphome/components/network/util.h index 0322f19215..5377d44f2f 100644 --- a/esphome/components/network/util.h +++ b/esphome/components/network/util.h @@ -12,7 +12,7 @@ bool is_connected(); bool is_disabled(); /// Get the active network hostname std::string get_use_address(); -IPAddress get_ip_address(); +IPAddresses get_ip_addresses(); } // namespace network } // namespace esphome diff --git a/esphome/components/nextion/base_component.py b/esphome/components/nextion/base_component.py index 5bd6643cb8..784da35371 100644 --- a/esphome/components/nextion/base_component.py +++ b/esphome/components/nextion/base_component.py @@ -33,14 +33,14 @@ CONF_EXIT_REPARSE_ON_START = "exit_reparse_on_start" def NextionName(value): - valid_chars = f"{ascii_letters + digits}." + valid_chars = f"{ascii_letters + digits + '_'}." if not isinstance(value, str) or len(value) > 29: raise cv.Invalid("Must be a string less than 29 characters") for char in value: if char not in valid_chars: raise cv.Invalid( - f"Must only consist of upper/lowercase characters, numbers and the period '.'. The character '{char}' cannot be used." + f"Must only consist of upper/lowercase characters, numbers, the underscore '_', and the period '.'. The character '{char}' cannot be used." ) return value diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index fd61dfa2be..27f2030f0d 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.components import display, uart +from esphome.components import esp32 from esphome.const import ( CONF_ID, CONF_LAMBDA, @@ -96,6 +97,11 @@ async def to_code(config): if CORE.is_esp32 and CORE.using_arduino: cg.add_library("WiFiClientSecure", None) cg.add_library("HTTPClient", None) + elif CORE.is_esp32 and CORE.using_esp_idf: + esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", True + ) elif CORE.is_esp8266 and CORE.using_arduino: cg.add_library("ESP8266HTTPClient", None) diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index acbf394fc6..f9f01de72c 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -82,16 +82,16 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe /** * Set the picture of an image component. * @param component The component name. - * @param value The picture name. + * @param value The picture id. * * Example: * ```cpp - * it.set_component_picture("pic", "4"); + * it.set_component_picture("pic", 4); * ``` * * This will change the image of the component `pic` to the image with ID `4`. */ - void set_component_picture(const char *component, const char *picture); + void set_component_picture(const char *component, uint8_t picture_id); /** * Set the background color of a component. * @param component The component name. @@ -750,6 +750,50 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe */ void filled_circle(int center_x, int center_y, int radius, Color color); + /** + * Draws a QR code in the screen + * @param x1 The top left x coordinate to start the QR code. + * @param y1 The top left y coordinate to start the QR code. + * @param content The content of the QR code (as a plain text - Nextion will generate the QR code). + * @param size The size (in pixels) for the QR code. Defaults to 200px. + * @param background_color The background color to draw with (as rgb565 integer). Defaults to 65535 (white). + * @param foreground_color The foreground color to draw with (as rgb565 integer). Defaults to 0 (black). + * @param logo_pic The picture id for the logo in the center of the QR code. Defaults to -1 (no logo). + * @param border_width The border width (in pixels) for the QR code. Defaults to 8px. + * + * Example: + * ```cpp + * it.qrcode(25, 25, "WIFI:S:MySSID;T:WPA;P:MyPassW0rd;;"); + * ``` + * + * Draws a QR code with a Wi-Fi network credentials starting at the given coordinates (25,25). + */ + void qrcode(int x1, int y1, const char *content, int size = 200, uint16_t background_color = 65535, + uint16_t foreground_color = 0, int logo_pic = -1, uint8_t border_width = 8); + /** + * Draws a QR code in the screen + * @param x1 The top left x coordinate to start the QR code. + * @param y1 The top left y coordinate to start the QR code. + * @param content The content of the QR code (as a plain text - Nextion will generate the QR code). + * @param size The size (in pixels) for the QR code. Defaults to 200px. + * @param background_color The background color to draw with (as Color). Defaults to 65535 (white). + * @param foreground_color The foreground color to draw with (as Color). Defaults to 0 (black). + * @param logo_pic The picture id for the logo in the center of the QR code. Defaults to -1 (no logo). + * @param border_width The border width (in pixels) for the QR code. Defaults to 8px. + * + * Example: + * ```cpp + * auto blue = Color(0, 0, 255); + * auto red = Color(255, 0, 0); + * it.qrcode(25, 25, "WIFI:S:MySSID;T:WPA;P:MyPassW0rd;;", 150, blue, red); + * ``` + * + * Draws a QR code with a Wi-Fi network credentials starting at the given coordinates (25,25) with size of 150px in + * red on a blue background. + */ + void qrcode(int x1, int y1, const char *content, int size, Color background_color = Color(255, 255, 255), + Color foreground_color = Color(0, 0, 0), int logo_pic = -1, uint8_t border_width = 8); + /** Set the brightness of the backlight. * * @param brightness The brightness percentage from 0 to 1.0. @@ -960,6 +1004,21 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe this->exit_reparse_on_start_ = exit_reparse_on_start; } + /** + * @brief Retrieves the number of commands pending in the Nextion command queue. + * + * This function returns the current count of commands that have been queued but not yet processed + * for the Nextion display. The Nextion command queue is used to store commands that are sent to + * the Nextion display for various operations like updating the display, changing interface elements, + * or other interactive features. A larger queue size might indicate a higher processing time or potential + * delays in command execution. This function is useful for monitoring the command flow and managing + * the execution efficiency of the Nextion display interface. + * + * @return size_t The number of commands currently in the Nextion queue. This count includes all commands + * that have been added to the queue and are awaiting processing. + */ + size_t queue_size() { return this->nextion_queue_.size(); } + protected: std::deque nextion_queue_; std::deque waveform_queue_; diff --git a/esphome/components/nextion/nextion_commands.cpp b/esphome/components/nextion/nextion_commands.cpp index 8512ea5573..e378111376 100644 --- a/esphome/components/nextion/nextion_commands.cpp +++ b/esphome/components/nextion/nextion_commands.cpp @@ -197,8 +197,8 @@ void Nextion::disable_component_touch(const char *component) { this->add_no_result_to_queue_with_printf_("disable_component_touch", "tsw %s,0", component); } -void Nextion::set_component_picture(const char *component, const char *picture) { - this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.val=%s", component, picture); +void Nextion::set_component_picture(const char *component, uint8_t picture_id) { + this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.pic=%d", component, picture_id); } void Nextion::set_component_text(const char *component, const char *text) { @@ -294,6 +294,19 @@ void Nextion::filled_circle(int center_x, int center_y, int radius, Color color) display::ColorUtil::color_to_565(color)); } +void Nextion::qrcode(int x1, int y1, const char *content, int size, uint16_t background_color, + uint16_t foreground_color, int logo_pic, uint8_t border_width) { + this->add_no_result_to_queue_with_printf_("qrcode", "qrcode %d,%d,%d,%d,%d,%d,%d,\"%s\"", x1, y1, size, + background_color, foreground_color, logo_pic, border_width, content); +} + +void Nextion::qrcode(int x1, int y1, const char *content, int size, Color background_color, Color foreground_color, + int logo_pic, uint8_t border_width) { + this->add_no_result_to_queue_with_printf_( + "qrcode", "qrcode %d,%d,%d,%d,%d,%d,%d,\"%s\"", x1, y1, size, display::ColorUtil::color_to_565(background_color), + display::ColorUtil::color_to_565(foreground_color), logo_pic, border_width, content); +} + void Nextion::set_nextion_rtc_time(ESPTime time) { this->add_no_result_to_queue_with_printf_("rtc0", "rtc0=%u", time.year); this->add_no_result_to_queue_with_printf_("rtc1", "rtc1=%u", time.month); diff --git a/esphome/components/nextion/nextion_upload_idf.cpp b/esphome/components/nextion/nextion_upload_idf.cpp index 57bb9c45e8..14b1b6cfaf 100644 --- a/esphome/components/nextion/nextion_upload_idf.cpp +++ b/esphome/components/nextion/nextion_upload_idf.cpp @@ -24,7 +24,7 @@ int Nextion::upload_range(const std::string &url, int range_start) { ESP_LOGVV(TAG, "url: %s", url.c_str()); uint range_size = this->tft_size_ - range_start; ESP_LOGVV(TAG, "tft_size_: %i", this->tft_size_); - ESP_LOGV(TAG, "Available heap: %u", esp_get_free_heap_size()); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); int range_end = (range_start == 0) ? std::min(this->tft_size_, 16383) : this->tft_size_; if (range_size <= 0 or range_end <= range_start) { ESP_LOGE(TAG, "Invalid range"); @@ -37,6 +37,8 @@ int Nextion::upload_range(const std::string &url, int range_start) { esp_http_client_config_t config = { .url = url.c_str(), .cert_pem = nullptr, + .disable_auto_redirect = false, + .max_redirection_count = 10, }; esp_http_client_handle_t client = esp_http_client_init(&config); @@ -44,7 +46,7 @@ int Nextion::upload_range(const std::string &url, int range_start) { sprintf(range_header, "bytes=%d-%d", range_start, range_end); ESP_LOGV(TAG, "Requesting range: %s", range_header); esp_http_client_set_header(client, "Range", range_header); - ESP_LOGVV(TAG, "Available heap: %u", esp_get_free_heap_size()); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); ESP_LOGV(TAG, "Opening http connetion"); esp_err_t err; @@ -65,18 +67,19 @@ int Nextion::upload_range(const std::string &url, int range_start) { int total_read_len = 0, read_len; + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); ESP_LOGV(TAG, "Allocate buffer"); uint8_t *buffer = new uint8_t[4096]; std::string recv_string; if (buffer == nullptr) { ESP_LOGE(TAG, "Failed to allocate memory for buffer"); - ESP_LOGV(TAG, "Available heap: %u", esp_get_free_heap_size()); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); } else { ESP_LOGV(TAG, "Memory for buffer allocated successfully"); while (true) { App.feed_wdt(); - ESP_LOGVV(TAG, "Available heap: %u", esp_get_free_heap_size()); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); int read_len = esp_http_client_read(client, reinterpret_cast(buffer), 4096); ESP_LOGVV(TAG, "Read %d bytes from HTTP client, writing to UART", read_len); if (read_len > 0) { @@ -84,15 +87,14 @@ int Nextion::upload_range(const std::string &url, int range_start) { ESP_LOGVV(TAG, "Write to UART successful"); this->recv_ret_string_(recv_string, 5000, true); this->content_length_ -= read_len; - ESP_LOGD(TAG, "Uploaded %0.2f %%, remaining %d bytes", - 100.0 * (this->tft_size_ - this->content_length_) / this->tft_size_, this->content_length_); - if (recv_string[0] != 0x05) { // 0x05 == "ok" + ESP_LOGD(TAG, "Uploaded %0.2f %%, remaining %d bytes, heap is %" PRIu32 " bytes", + 100.0 * (this->tft_size_ - this->content_length_) / this->tft_size_, this->content_length_, + esp_get_free_heap_size()); + + if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request ESP_LOGD( TAG, "recv_string [%s]", format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); - } - // handle partial upload request - if (recv_string[0] == 0x08 && recv_string.size() == 5) { uint32_t result = 0; for (int j = 0; j < 4; ++j) { result += static_cast(recv_string[j + 1]) << (8 * j); @@ -101,13 +103,37 @@ int Nextion::upload_range(const std::string &url, int range_start) { ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result); this->content_length_ = this->tft_size_ - result; // Deallocate the buffer when done + ESP_LOGV(TAG, "Deallocate buffer"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); delete[] buffer; ESP_LOGVV(TAG, "Memory for buffer deallocated"); - esp_http_client_cleanup(client); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGV(TAG, "Close http client"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); esp_http_client_close(client); + esp_http_client_cleanup(client); + ESP_LOGVV(TAG, "Client closed"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); return result; } + } else if (recv_string[0] != 0x05) { // 0x05 == "ok" + ESP_LOGE( + TAG, "Invalid response from Nextion: [%s]", + format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + ESP_LOGV(TAG, "Deallocate buffer"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); + delete[] buffer; + ESP_LOGVV(TAG, "Memory for buffer deallocated"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGV(TAG, "Close http client"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); + esp_http_client_close(client); + esp_http_client_cleanup(client); + ESP_LOGVV(TAG, "Client closed"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); + return -1; } + recv_string.clear(); } else if (read_len == 0) { ESP_LOGV(TAG, "End of HTTP response reached"); @@ -119,11 +145,18 @@ int Nextion::upload_range(const std::string &url, int range_start) { } // Deallocate the buffer when done + ESP_LOGV(TAG, "Deallocate buffer"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); delete[] buffer; ESP_LOGVV(TAG, "Memory for buffer deallocated"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); } - esp_http_client_cleanup(client); + ESP_LOGV(TAG, "Close http client"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); esp_http_client_close(client); + esp_http_client_cleanup(client); + ESP_LOGVV(TAG, "Client closed"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); return range_end + 1; } @@ -145,17 +178,19 @@ bool Nextion::upload_tft() { // Define the configuration for the HTTP client ESP_LOGV(TAG, "Establishing connection to HTTP server"); - ESP_LOGVV(TAG, "Available heap: %u", esp_get_free_heap_size()); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); esp_http_client_config_t config = { .url = this->tft_url_.c_str(), .cert_pem = nullptr, .method = HTTP_METHOD_HEAD, .timeout_ms = 15000, + .disable_auto_redirect = false, + .max_redirection_count = 10, }; // Initialize the HTTP client with the configuration ESP_LOGV(TAG, "Initializing HTTP client"); - ESP_LOGV(TAG, "Available heap: %u", esp_get_free_heap_size()); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); esp_http_client_handle_t http = esp_http_client_init(&config); if (!http) { ESP_LOGE(TAG, "Failed to initialize HTTP client."); @@ -164,7 +199,7 @@ bool Nextion::upload_tft() { // Perform the HTTP request ESP_LOGV(TAG, "Check if the client could connect"); - ESP_LOGV(TAG, "Available heap: %u", esp_get_free_heap_size()); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); esp_err_t err = esp_http_client_perform(http); if (err != ESP_OK) { ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err)); @@ -173,14 +208,22 @@ bool Nextion::upload_tft() { } // Check the HTTP Status Code + ESP_LOGV(TAG, "Check the HTTP Status Code"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); int status_code = esp_http_client_get_status_code(http); ESP_LOGV(TAG, "HTTP Status Code: %d", status_code); size_t tft_file_size = esp_http_client_get_content_length(http); ESP_LOGD(TAG, "TFT file size: %zu", tft_file_size); + ESP_LOGD(TAG, "Close HTTP connection"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); + esp_http_client_close(http); + esp_http_client_cleanup(http); + ESP_LOGVV(TAG, "Connection closed"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); + if (tft_file_size < 4096) { ESP_LOGE(TAG, "File size check failed. Size: %zu", tft_file_size); - esp_http_client_cleanup(http); return this->upload_end(false); } else { ESP_LOGV(TAG, "File size check passed. Proceeding..."); @@ -189,8 +232,10 @@ bool Nextion::upload_tft() { this->tft_size_ = tft_file_size; ESP_LOGD(TAG, "Updating Nextion"); - // The Nextion will ignore the update command if it is sleeping + // The Nextion will ignore the update command if it is sleeping + ESP_LOGV(TAG, "Wake-up Nextion"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); this->send_command_("sleep=0"); this->set_backlight_brightness(1.0); vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT @@ -203,26 +248,31 @@ bool Nextion::upload_tft() { sprintf(command, "whmi-wris %d,%" PRIu32 ",1", this->content_length_, this->parent_->get_baud_rate()); // Clear serial receive buffer + ESP_LOGV(TAG, "Clear serial receive buffer"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); uint8_t d; while (this->available()) { this->read_byte(&d); }; + ESP_LOGV(TAG, "Send update instruction: %s", command); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); this->send_command_(command); std::string response; ESP_LOGV(TAG, "Waiting for upgrade response"); - this->recv_ret_string_(response, 2048, true); // This can take some time to return + this->recv_ret_string_(response, 5000, true); // This can take some time to return // The Nextion display will, if it's ready to accept data, send a 0x05 byte. - ESP_LOGD(TAG, "Upgrade response is [%s]", - format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str()); + ESP_LOGD(TAG, "Upgrade response is [%s] - %zu bytes", + format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str(), + response.length()); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); if (response.find(0x05) != std::string::npos) { ESP_LOGV(TAG, "Preparation for tft update done"); } else { ESP_LOGE(TAG, "Preparation for tft update failed %d \"%s\"", response[0], response.c_str()); - esp_http_client_cleanup(http); return this->upload_end(false); } @@ -230,12 +280,12 @@ bool Nextion::upload_tft() { content_length_, esp_get_free_heap_size()); ESP_LOGV(TAG, "Starting transfer by chunks loop"); + ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); int result = 0; while (content_length_ > 0) { result = upload_range(this->tft_url_.c_str(), result); if (result < 0) { ESP_LOGE(TAG, "Error updating Nextion!"); - esp_http_client_cleanup(http); return this->upload_end(false); } App.feed_wdt(); @@ -244,9 +294,6 @@ bool Nextion::upload_tft() { ESP_LOGD(TAG, "Successfully updated Nextion!"); - ESP_LOGD(TAG, "Close HTTP connection"); - esp_http_client_close(http); - esp_http_client_cleanup(http); return upload_end(true); } @@ -256,7 +303,7 @@ bool Nextion::upload_end(bool successful) { this->soft_reset(); vTaskDelay(pdMS_TO_TICKS(1500)); // NOLINT if (successful) { - ESP_LOGD(TAG, "Restarting esphome"); + ESP_LOGD(TAG, "Restarting ESPHome"); esp_restart(); // NOLINT(readability-static-accessed-through-instance) } return successful; diff --git a/esphome/components/nfc/__init__.py b/esphome/components/nfc/__init__.py index c3bbc50bf9..eea1a47b24 100644 --- a/esphome/components/nfc/__init__.py +++ b/esphome/components/nfc/__init__.py @@ -1,12 +1,13 @@ from esphome import automation import esphome.codegen as cg -CODEOWNERS = ["@jesserockz"] +CODEOWNERS = ["@jesserockz", "@kbx81"] nfc_ns = cg.esphome_ns.namespace("nfc") +Nfcc = nfc_ns.class_("Nfcc") NfcTag = nfc_ns.class_("NfcTag") - +NfcTagListener = nfc_ns.class_("NfcTagListener") NfcOnTagTrigger = nfc_ns.class_( "NfcOnTagTrigger", automation.Trigger.template(cg.std_string, NfcTag) ) diff --git a/esphome/components/nfc/binary_sensor/__init__.py b/esphome/components/nfc/binary_sensor/__init__.py new file mode 100644 index 0000000000..21c8298ea8 --- /dev/null +++ b/esphome/components/nfc/binary_sensor/__init__.py @@ -0,0 +1,72 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_UID +from esphome.core import HexInt +from .. import nfc_ns, Nfcc, NfcTagListener + +DEPENDENCIES = ["nfc"] + +CONF_NDEF_CONTAINS = "ndef_contains" +CONF_NFCC_ID = "nfcc_id" +CONF_TAG_ID = "tag_id" + +NfcTagBinarySensor = nfc_ns.class_( + "NfcTagBinarySensor", + binary_sensor.BinarySensor, + cg.Component, + NfcTagListener, + cg.Parented.template(Nfcc), +) + + +def validate_uid(value): + value = cv.string_strict(value) + for x in value.split("-"): + if len(x) != 2: + raise cv.Invalid( + "Each part (separated by '-') of the UID must be two characters " + "long." + ) + try: + x = int(x, 16) + except ValueError as err: + raise cv.Invalid( + "Valid characters for parts of a UID are 0123456789ABCDEF." + ) from err + if x < 0 or x > 255: + raise cv.Invalid( + "Valid values for UID parts (separated by '-') are 00 to FF" + ) + return value + + +CONFIG_SCHEMA = cv.All( + binary_sensor.binary_sensor_schema(NfcTagBinarySensor) + .extend( + { + cv.GenerateID(CONF_NFCC_ID): cv.use_id(Nfcc), + cv.Optional(CONF_NDEF_CONTAINS): cv.string, + cv.Optional(CONF_TAG_ID): cv.string, + cv.Optional(CONF_UID): validate_uid, + } + ) + .extend(cv.COMPONENT_SCHEMA), + cv.has_exactly_one_key(CONF_NDEF_CONTAINS, CONF_TAG_ID, CONF_UID), +) + + +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_NFCC_ID]) + + hub = await cg.get_variable(config[CONF_NFCC_ID]) + cg.add(hub.register_listener(var)) + if CONF_NDEF_CONTAINS in config: + cg.add(var.set_ndef_match_string(config[CONF_NDEF_CONTAINS])) + if CONF_TAG_ID in config: + cg.add(var.set_tag_name(config[CONF_TAG_ID])) + elif CONF_UID in config: + addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split("-")] + cg.add(var.set_uid(addr)) diff --git a/esphome/components/nfc/binary_sensor/binary_sensor.cpp b/esphome/components/nfc/binary_sensor/binary_sensor.cpp new file mode 100644 index 0000000000..8f1f6acd51 --- /dev/null +++ b/esphome/components/nfc/binary_sensor/binary_sensor.cpp @@ -0,0 +1,114 @@ +#include "binary_sensor.h" +#include "../nfc_helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nfc { + +static const char *const TAG = "nfc.binary_sensor"; + +void NfcTagBinarySensor::setup() { + this->parent_->register_listener(this); + this->publish_initial_state(false); +} + +void NfcTagBinarySensor::dump_config() { + std::string match_str = "name"; + + LOG_BINARY_SENSOR("", "NFC Tag Binary Sensor", this); + if (!this->match_string_.empty()) { + if (!this->match_tag_name_) { + match_str = "contains"; + } + ESP_LOGCONFIG(TAG, " Tag %s: %s", match_str.c_str(), this->match_string_.c_str()); + return; + } + if (!this->uid_.empty()) { + ESP_LOGCONFIG(TAG, " Tag UID: %s", format_bytes(this->uid_).c_str()); + } +} + +void NfcTagBinarySensor::set_ndef_match_string(const std::string &str) { + this->match_string_ = str; + this->match_tag_name_ = false; +} + +void NfcTagBinarySensor::set_tag_name(const std::string &str) { + this->match_string_ = str; + this->match_tag_name_ = true; +} + +void NfcTagBinarySensor::set_uid(const std::vector &uid) { this->uid_ = uid; } + +bool NfcTagBinarySensor::tag_match_ndef_string(const std::shared_ptr &msg) { + for (const auto &record : msg->get_records()) { + if (record->get_payload().find(this->match_string_) != std::string::npos) { + return true; + } + } + return false; +} + +bool NfcTagBinarySensor::tag_match_tag_name(const std::shared_ptr &msg) { + for (const auto &record : msg->get_records()) { + if (record->get_payload().find(HA_TAG_ID_PREFIX) != std::string::npos) { + auto rec_substr = record->get_payload().substr(sizeof(HA_TAG_ID_PREFIX) - 1); + if (rec_substr.find(this->match_string_) != std::string::npos) { + return true; + } + } + } + return false; +} + +bool NfcTagBinarySensor::tag_match_uid(const std::vector &data) { + if (data.size() != this->uid_.size()) { + return false; + } + + for (size_t i = 0; i < data.size(); i++) { + if (data[i] != this->uid_[i]) { + return false; + } + } + return true; +} + +void NfcTagBinarySensor::tag_off(NfcTag &tag) { + if (!this->match_string_.empty() && tag.has_ndef_message()) { + if (this->match_tag_name_) { + if (this->tag_match_tag_name(tag.get_ndef_message())) { + this->publish_state(false); + } + } else { + if (this->tag_match_ndef_string(tag.get_ndef_message())) { + this->publish_state(false); + } + } + return; + } + if (!this->uid_.empty() && this->tag_match_uid(tag.get_uid())) { + this->publish_state(false); + } +} + +void NfcTagBinarySensor::tag_on(NfcTag &tag) { + if (!this->match_string_.empty() && tag.has_ndef_message()) { + if (this->match_tag_name_) { + if (this->tag_match_tag_name(tag.get_ndef_message())) { + this->publish_state(true); + } + } else { + if (this->tag_match_ndef_string(tag.get_ndef_message())) { + this->publish_state(true); + } + } + return; + } + if (!this->uid_.empty() && this->tag_match_uid(tag.get_uid())) { + this->publish_state(true); + } +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/binary_sensor/binary_sensor.h b/esphome/components/nfc/binary_sensor/binary_sensor.h new file mode 100644 index 0000000000..cc313c2f2b --- /dev/null +++ b/esphome/components/nfc/binary_sensor/binary_sensor.h @@ -0,0 +1,38 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/nfc/nfc.h" +#include "esphome/components/nfc/nfc_tag.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace nfc { + +class NfcTagBinarySensor : public binary_sensor::BinarySensor, + public Component, + public NfcTagListener, + public Parented { + public: + void setup() override; + void dump_config() override; + + void set_ndef_match_string(const std::string &str); + void set_tag_name(const std::string &str); + void set_uid(const std::vector &uid); + + bool tag_match_ndef_string(const std::shared_ptr &msg); + bool tag_match_tag_name(const std::shared_ptr &msg); + bool tag_match_uid(const std::vector &data); + + void tag_off(NfcTag &tag) override; + void tag_on(NfcTag &tag) override; + + protected: + bool match_tag_name_{false}; + std::string match_string_; + std::vector uid_; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nfc.h b/esphome/components/nfc/nfc.h index d4d66f970f..23bfdd8ef0 100644 --- a/esphome/components/nfc/nfc.h +++ b/esphome/components/nfc/nfc.h @@ -66,5 +66,19 @@ bool mifare_classic_is_trailer_block(uint8_t block_num); uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length); +class NfcTagListener { + public: + virtual void tag_off(NfcTag &tag) {} + virtual void tag_on(NfcTag &tag) {} +}; + +class Nfcc { + public: + void register_listener(NfcTagListener *listener) { this->tag_listeners_.push_back(listener); } + + protected: + std::vector tag_listeners_; +}; + } // namespace nfc } // namespace esphome diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 07164be5ce..ecc2ab2ee7 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -62,6 +62,7 @@ from esphome.const import ( DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLUME, + DEVICE_CLASS_VOLUME_FLOW_RATE, DEVICE_CLASS_VOLUME_STORAGE, DEVICE_CLASS_WATER, DEVICE_CLASS_WEIGHT, @@ -117,6 +118,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLUME, + DEVICE_CLASS_VOLUME_FLOW_RATE, DEVICE_CLASS_VOLUME_STORAGE, DEVICE_CLASS_WATER, DEVICE_CLASS_WEIGHT, diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 039596d897..3c845490dc 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -12,6 +12,7 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_OTA, KEY_PAST_SAFE_MODE, + CONF_VERSION, ) from esphome.core import CORE, coroutine_with_priority @@ -41,6 +42,7 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(OTAComponent), cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean, + cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True), cv.SplitDefault( CONF_PORT, esp8266=8266, @@ -93,6 +95,7 @@ async def to_code(config): if CONF_PASSWORD in config: cg.add(var.set_auth_password(config[CONF_PASSWORD])) cg.add_define("USE_OTA_PASSWORD") + cg.add_define("USE_OTA_VERSION", config[CONF_VERSION]) await cg.register_component(var, config) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 41cf333be9..15af14ff1a 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -20,8 +20,7 @@ namespace esphome { namespace ota { static const char *const TAG = "ota"; - -static const uint8_t OTA_VERSION_1_0 = 1; +static constexpr u_int16_t OTA_BLOCK_SIZE = 8192; OTAComponent *global_ota_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -101,6 +100,7 @@ void OTAComponent::dump_config() { ESP_LOGCONFIG(TAG, " Using Password."); } #endif + ESP_LOGCONFIG(TAG, " OTA version: %d.", USE_OTA_VERSION); if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 && this->safe_mode_rtc_value_ != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) { ESP_LOGW(TAG, "Last Boot was an unhandled reset, will proceed to safe mode in %" PRIu32 " restarts", @@ -132,6 +132,9 @@ void OTAComponent::handle_() { uint8_t ota_features; std::unique_ptr backend; (void) ota_features; +#if USE_OTA_VERSION == 2 + size_t size_acknowledged = 0; +#endif if (client_ == nullptr) { struct sockaddr_storage source_addr; @@ -168,7 +171,7 @@ void OTAComponent::handle_() { // Send OK and version - 2 bytes buf[0] = OTA_RESPONSE_OK; - buf[1] = OTA_VERSION_1_0; + buf[1] = USE_OTA_VERSION; this->writeall_(buf, 2); backend = make_ota_backend(); @@ -312,6 +315,13 @@ void OTAComponent::handle_() { goto error; // NOLINT(cppcoreguidelines-avoid-goto) } total += read; +#if USE_OTA_VERSION == 2 + while (size_acknowledged + OTA_BLOCK_SIZE <= total || (total == ota_size && size_acknowledged < ota_size)) { + buf[0] = OTA_RESPONSE_CHUNK_OK; + this->writeall_(buf, 1); + size_acknowledged += OTA_BLOCK_SIZE; + } +#endif uint32_t now = millis(); if (now - last_progress > 1000) { diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index 50d095be6c..c20f4f0709 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -10,31 +10,32 @@ namespace esphome { namespace ota { enum OTAResponseTypes { - OTA_RESPONSE_OK = 0, - OTA_RESPONSE_REQUEST_AUTH = 1, + OTA_RESPONSE_OK = 0x00, + OTA_RESPONSE_REQUEST_AUTH = 0x01, - OTA_RESPONSE_HEADER_OK = 64, - OTA_RESPONSE_AUTH_OK = 65, - OTA_RESPONSE_UPDATE_PREPARE_OK = 66, - OTA_RESPONSE_BIN_MD5_OK = 67, - OTA_RESPONSE_RECEIVE_OK = 68, - OTA_RESPONSE_UPDATE_END_OK = 69, - OTA_RESPONSE_SUPPORTS_COMPRESSION = 70, + OTA_RESPONSE_HEADER_OK = 0x40, + OTA_RESPONSE_AUTH_OK = 0x41, + OTA_RESPONSE_UPDATE_PREPARE_OK = 0x42, + OTA_RESPONSE_BIN_MD5_OK = 0x43, + OTA_RESPONSE_RECEIVE_OK = 0x44, + OTA_RESPONSE_UPDATE_END_OK = 0x45, + OTA_RESPONSE_SUPPORTS_COMPRESSION = 0x46, + OTA_RESPONSE_CHUNK_OK = 0x47, - OTA_RESPONSE_ERROR_MAGIC = 128, - OTA_RESPONSE_ERROR_UPDATE_PREPARE = 129, - OTA_RESPONSE_ERROR_AUTH_INVALID = 130, - OTA_RESPONSE_ERROR_WRITING_FLASH = 131, - OTA_RESPONSE_ERROR_UPDATE_END = 132, - OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 133, - OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 134, - OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135, - OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136, - OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137, - OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138, - OTA_RESPONSE_ERROR_MD5_MISMATCH = 139, - OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 140, - OTA_RESPONSE_ERROR_UNKNOWN = 255, + OTA_RESPONSE_ERROR_MAGIC = 0x80, + OTA_RESPONSE_ERROR_UPDATE_PREPARE = 0x81, + OTA_RESPONSE_ERROR_AUTH_INVALID = 0x82, + OTA_RESPONSE_ERROR_WRITING_FLASH = 0x83, + OTA_RESPONSE_ERROR_UPDATE_END = 0x84, + OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 0x85, + OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 0x86, + OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 0x87, + OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 0x88, + OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 0x89, + OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 0x8A, + OTA_RESPONSE_ERROR_MD5_MISMATCH = 0x8B, + OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 0x8C, + OTA_RESPONSE_ERROR_UNKNOWN = 0xFF, }; enum OTAState { OTA_COMPLETED = 0, OTA_STARTED, OTA_IN_PROGRESS, OTA_ERROR }; diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index 04aba4382b..de2b23b8eb 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -195,7 +195,7 @@ void PMSX003Component::send_command_(uint8_t cmd, uint16_t data) { void PMSX003Component::parse_data_() { switch (this->type_) { case PMSX003_TYPE_5003ST: { - float temperature = this->get_16_bit_uint_(30) / 10.0f; + float temperature = (int16_t) this->get_16_bit_uint_(30) / 10.0f; float humidity = this->get_16_bit_uint_(32) / 10.0f; ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%%", temperature, humidity); @@ -279,7 +279,7 @@ void PMSX003Component::parse_data_() { // Note the pm particles 50um & 100um are not returned, // as PMS5003T uses those data values for temperature and humidity. - float temperature = this->get_16_bit_uint_(24) / 10.0f; + float temperature = (int16_t) this->get_16_bit_uint_(24) / 10.0f; float humidity = this->get_16_bit_uint_(26) / 10.0f; ESP_LOGD(TAG, diff --git a/esphome/components/pmsx003/pmsx003.h b/esphome/components/pmsx003/pmsx003.h index eb33f66909..cb5c16aecf 100644 --- a/esphome/components/pmsx003/pmsx003.h +++ b/esphome/components/pmsx003/pmsx003.h @@ -1,16 +1,17 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/uart/uart.h" +#include "esphome/core/component.h" namespace esphome { namespace pmsx003 { // known command bytes -#define PMS_CMD_AUTO_MANUAL 0xE1 // data=0: perform measurement manually, data=1: perform measurement automatically -#define PMS_CMD_TRIG_MANUAL 0xE2 // trigger a manual measurement -#define PMS_CMD_ON_STANDBY 0xE4 // data=0: go to standby mode, data=1: go to normal mode +static const uint8_t PMS_CMD_AUTO_MANUAL = + 0xE1; // data=0: perform measurement manually, data=1: perform measurement automatically +static const uint8_t PMS_CMD_TRIG_MANUAL = 0xE2; // trigger a manual measurement +static const uint8_t PMS_CMD_ON_STANDBY = 0xE4; // data=0: go to standby mode, data=1: go to normal mode static const uint16_t PMS_STABILISING_MS = 30000; // time taken for the sensor to become stable after power on diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index eefcb529f2..08ccd6096e 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -92,66 +92,78 @@ CONFIG_SCHEMA = ( icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, device_class=DEVICE_CLASS_PM1, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5_STD): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, device_class=DEVICE_CLASS_PM25, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0_STD): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, device_class=DEVICE_CLASS_PM10, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_1_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM1, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM25, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM10, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_0_3UM): sensor.sensor_schema( unit_of_measurement=UNIT_COUNT_DECILITRE, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_0_5UM): sensor.sensor_schema( unit_of_measurement=UNIT_COUNT_DECILITRE, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_1_0UM): sensor.sensor_schema( unit_of_measurement=UNIT_COUNT_DECILITRE, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5UM): sensor.sensor_schema( unit_of_measurement=UNIT_COUNT_DECILITRE, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_5_0UM): sensor.sensor_schema( unit_of_measurement=UNIT_COUNT_DECILITRE, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0UM): sensor.sensor_schema( unit_of_measurement=UNIT_COUNT_DECILITRE, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, diff --git a/esphome/components/pn7150/__init__.py b/esphome/components/pn7150/__init__.py index 3b80b574e9..a136028011 100644 --- a/esphome/components/pn7150/__init__.py +++ b/esphome/components/pn7150/__init__.py @@ -34,7 +34,7 @@ CONF_TAG_TTL = "tag_ttl" CONF_VEN_PIN = "ven_pin" pn7150_ns = cg.esphome_ns.namespace("pn7150") -PN7150 = pn7150_ns.class_("PN7150", cg.Component) +PN7150 = pn7150_ns.class_("PN7150", nfc.Nfcc, cg.Component) EmulationOffAction = pn7150_ns.class_("EmulationOffAction", automation.Action) EmulationOnAction = pn7150_ns.class_("EmulationOnAction", automation.Action) diff --git a/esphome/components/pn7150/pn7150.cpp b/esphome/components/pn7150/pn7150.cpp index 6703ab6a12..be4d6c1bb7 100644 --- a/esphome/components/pn7150/pn7150.cpp +++ b/esphome/components/pn7150/pn7150.cpp @@ -566,6 +566,9 @@ void PN7150::erase_tag_(const uint8_t tag_index) { for (auto *trigger : this->triggers_ontagremoved_) { trigger->process(this->discovered_endpoint_[tag_index].tag); } + for (auto *listener : this->tag_listeners_) { + listener->tag_off(*this->discovered_endpoint_[tag_index].tag); + } ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str()); this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index); } @@ -881,6 +884,9 @@ void PN7150::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi for (auto *trigger : this->triggers_ontag_) { trigger->process(working_endpoint.tag); } + for (auto *listener : this->tag_listeners_) { + listener->tag_on(*working_endpoint.tag); + } working_endpoint.trig_called = true; break; } diff --git a/esphome/components/pn7150/pn7150.h b/esphome/components/pn7150/pn7150.h index 4aad4e1720..54038f5085 100644 --- a/esphome/components/pn7150/pn7150.h +++ b/esphome/components/pn7150/pn7150.h @@ -142,7 +142,7 @@ struct DiscoveredEndpoint { bool trig_called; }; -class PN7150 : public Component { +class PN7150 : public nfc::Nfcc, public Component { public: void setup() override; void dump_config() override; diff --git a/esphome/components/pn7160/__init__.py b/esphome/components/pn7160/__init__.py index c91ca78b03..1639041b9e 100644 --- a/esphome/components/pn7160/__init__.py +++ b/esphome/components/pn7160/__init__.py @@ -36,7 +36,7 @@ CONF_VEN_PIN = "ven_pin" CONF_WKUP_REQ_PIN = "wkup_req_pin" pn7160_ns = cg.esphome_ns.namespace("pn7160") -PN7160 = pn7160_ns.class_("PN7160", cg.Component) +PN7160 = pn7160_ns.class_("PN7160", nfc.Nfcc, cg.Component) EmulationOffAction = pn7160_ns.class_("EmulationOffAction", automation.Action) EmulationOnAction = pn7160_ns.class_("EmulationOnAction", automation.Action) diff --git a/esphome/components/pn7160/pn7160.cpp b/esphome/components/pn7160/pn7160.cpp index ce5374d1d1..a7d3b38fb7 100644 --- a/esphome/components/pn7160/pn7160.cpp +++ b/esphome/components/pn7160/pn7160.cpp @@ -591,6 +591,9 @@ void PN7160::erase_tag_(const uint8_t tag_index) { for (auto *trigger : this->triggers_ontagremoved_) { trigger->process(this->discovered_endpoint_[tag_index].tag); } + for (auto *listener : this->tag_listeners_) { + listener->tag_off(*this->discovered_endpoint_[tag_index].tag); + } ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str()); this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index); } @@ -905,6 +908,9 @@ void PN7160::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi for (auto *trigger : this->triggers_ontag_) { trigger->process(working_endpoint.tag); } + for (auto *listener : this->tag_listeners_) { + listener->tag_on(*working_endpoint.tag); + } working_endpoint.trig_called = true; break; } diff --git a/esphome/components/pn7160/pn7160.h b/esphome/components/pn7160/pn7160.h index 2b3cb99453..f2e05ea1d0 100644 --- a/esphome/components/pn7160/pn7160.h +++ b/esphome/components/pn7160/pn7160.h @@ -157,7 +157,7 @@ struct DiscoveredEndpoint { bool trig_called; }; -class PN7160 : public Component { +class PN7160 : public nfc::Nfcc, public Component { public: void setup() override; void dump_config() override; diff --git a/esphome/components/pylontech/__init__.py b/esphome/components/pylontech/__init__.py index 56fac92e89..197f7e7904 100644 --- a/esphome/components/pylontech/__init__.py +++ b/esphome/components/pylontech/__init__.py @@ -19,7 +19,7 @@ PylontechComponent = pylontech_ns.class_( ) PylontechBattery = pylontech_ns.class_("PylontechBattery") -CV_NUM_BATTERIES = cv.int_range(1, 6) +CV_NUM_BATTERIES = cv.int_range(1, 16) PYLONTECH_COMPONENT_SCHEMA = cv.Schema( { diff --git a/esphome/components/pylontech/pylontech.cpp b/esphome/components/pylontech/pylontech.cpp index 4bfa876110..b33f4d4874 100644 --- a/esphome/components/pylontech/pylontech.cpp +++ b/esphome/components/pylontech/pylontech.cpp @@ -1,5 +1,6 @@ #include "pylontech.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" namespace esphome { namespace pylontech { @@ -34,26 +35,30 @@ void PylontechComponent::setup() { void PylontechComponent::update() { this->write_str("pwr\n"); } void PylontechComponent::loop() { - uint8_t data; - - // pylontech sends a lot of data very suddenly - // we need to quickly put it all into our own buffer, otherwise the uart's buffer will overflow - while (this->available() > 0) { - if (this->read_byte(&data)) { - buffer_[buffer_index_write_] += (char) data; - if (buffer_[buffer_index_write_].back() == static_cast(ASCII_LF) || - buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) { - // complete line received - buffer_index_write_ = (buffer_index_write_ + 1) % NUM_BUFFERS; + if (this->available() > 0) { + // pylontech sends a lot of data very suddenly + // we need to quickly put it all into our own buffer, otherwise the uart's buffer will overflow + uint8_t data; + int recv = 0; + while (this->available() > 0) { + if (this->read_byte(&data)) { + buffer_[buffer_index_write_] += (char) data; + recv++; + if (buffer_[buffer_index_write_].back() == static_cast(ASCII_LF) || + buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) { + // complete line received + buffer_index_write_ = (buffer_index_write_ + 1) % NUM_BUFFERS; + } } } - } - - // only process one line per call of loop() to not block esphome for too long - if (buffer_index_read_ != buffer_index_write_) { - this->process_line_(buffer_[buffer_index_read_]); - buffer_[buffer_index_read_].clear(); - buffer_index_read_ = (buffer_index_read_ + 1) % NUM_BUFFERS; + ESP_LOGV(TAG, "received %d bytes", recv); + } else { + // only process one line per call of loop() to not block esphome for too long + if (buffer_index_read_ != buffer_index_write_) { + this->process_line_(buffer_[buffer_index_read_]); + buffer_[buffer_index_read_].clear(); + buffer_index_read_ = (buffer_index_read_ + 1) % NUM_BUFFERS; + } } } @@ -66,10 +71,11 @@ void PylontechComponent::process_line_(std::string &buffer) { // clang-format on PylontechListener::LineContents l{}; - const int parsed = sscanf( // NOLINT - buffer.c_str(), "%d %d %d %d %d %d %d %d %7s %7s %7s %7s %d%% %*d-%*d-%*d %*d:%*d:%*d %*s %*s %d %*s", // NOLINT - &l.bat_num, &l.volt, &l.curr, &l.tempr, &l.tlow, &l.thigh, &l.vlow, &l.vhigh, l.base_st, l.volt_st, // NOLINT - l.curr_st, l.temp_st, &l.coulomb, &l.mostempr); // NOLINT + char mostempr_s[6]; + const int parsed = sscanf( // NOLINT + buffer.c_str(), "%d %d %d %d %d %d %d %d %7s %7s %7s %7s %d%% %*d-%*d-%*d %*d:%*d:%*d %*s %*s %5s %*s", // NOLINT + &l.bat_num, &l.volt, &l.curr, &l.tempr, &l.tlow, &l.thigh, &l.vlow, &l.vhigh, l.base_st, l.volt_st, // NOLINT + l.curr_st, l.temp_st, &l.coulomb, mostempr_s); // NOLINT if (l.bat_num <= 0) { ESP_LOGD(TAG, "invalid bat_num in line %s", buffer.substr(0, buffer.size() - 2).c_str()); @@ -79,6 +85,13 @@ void PylontechComponent::process_line_(std::string &buffer) { ESP_LOGW(TAG, "invalid line: found only %d items in %s", parsed, buffer.substr(0, buffer.size() - 2).c_str()); return; } + auto mostempr_parsed = parse_number(mostempr_s); + if (mostempr_parsed.has_value()) { + l.mostempr = mostempr_parsed.value(); + } else { + l.mostempr = -300; + ESP_LOGW(TAG, "bat_num %d: received no mostempr", l.bat_num); + } for (PylontechListener *listener : this->listeners_) { listener->on_line_read(&l); diff --git a/esphome/components/pylontech/sensor/__init__.py b/esphome/components/pylontech/sensor/__init__.py index 0423f3370c..a1477c627f 100644 --- a/esphome/components/pylontech/sensor/__init__.py +++ b/esphome/components/pylontech/sensor/__init__.py @@ -59,14 +59,14 @@ TYPES: dict[str, cv.Schema] = { device_class=DEVICE_CLASS_TEMPERATURE, ), CONF_VOLTAGE_LOW: sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - accuracy_decimals=1, - device_class=DEVICE_CLASS_TEMPERATURE, + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_VOLTAGE_HIGH: sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - accuracy_decimals=1, - device_class=DEVICE_CLASS_TEMPERATURE, + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_COULOMB: sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, diff --git a/esphome/components/qmc5883l/qmc5883l.cpp b/esphome/components/qmc5883l/qmc5883l.cpp index f03b6af191..4946ad1b77 100644 --- a/esphome/components/qmc5883l/qmc5883l.cpp +++ b/esphome/components/qmc5883l/qmc5883l.cpp @@ -72,19 +72,13 @@ void QMC5883LComponent::dump_config() { LOG_SENSOR(" ", "Y Axis", this->y_sensor_); LOG_SENSOR(" ", "Z Axis", this->z_sensor_); LOG_SENSOR(" ", "Heading", this->heading_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); } float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; } void QMC5883LComponent::update() { uint8_t status = false; - this->read_byte(QMC5883L_REGISTER_STATUS, &status); - - uint16_t raw_x, raw_y, raw_z; - if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x) || - !this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y) || - !this->read_byte_16_(QMC5883L_REGISTER_DATA_Z_LSB, &raw_z)) { - this->status_set_warning(); - return; - } + if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG) + this->read_byte(QMC5883L_REGISTER_STATUS, &status); float mg_per_bit; switch (this->range_) { @@ -99,12 +93,49 @@ void QMC5883LComponent::update() { } // in µT - const float x = int16_t(raw_x) * mg_per_bit * 0.1f; - const float y = int16_t(raw_y) * mg_per_bit * 0.1f; - const float z = int16_t(raw_z) * mg_per_bit * 0.1f; + float x = NAN, y = NAN, z = NAN; + if (this->x_sensor_ != nullptr || this->heading_sensor_ != nullptr) { + uint16_t raw_x; + if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x)) { + this->status_set_warning(); + return; + } + x = int16_t(raw_x) * mg_per_bit * 0.1f; + } + if (this->y_sensor_ != nullptr || this->heading_sensor_ != nullptr) { + uint16_t raw_y; + if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y)) { + this->status_set_warning(); + return; + } + y = int16_t(raw_y) * mg_per_bit * 0.1f; + } + if (this->z_sensor_ != nullptr) { + uint16_t raw_z; + if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_Z_LSB, &raw_z)) { + this->status_set_warning(); + return; + } + z = int16_t(raw_z) * mg_per_bit * 0.1f; + } - float heading = atan2f(0.0f - x, y) * 180.0f / M_PI; - ESP_LOGD(TAG, "Got x=%0.02fµT y=%0.02fµT z=%0.02fµT heading=%0.01f° status=%u", x, y, z, heading, status); + float heading = NAN; + if (this->heading_sensor_ != nullptr) { + heading = atan2f(0.0f - x, y) * 180.0f / M_PI; + } + + float temp = NAN; + if (this->temperature_sensor_ != nullptr) { + uint16_t raw_temp; + if (!this->read_byte_16_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp)) { + this->status_set_warning(); + return; + } + temp = int16_t(raw_temp) * 0.01f; + } + + ESP_LOGD(TAG, "Got x=%0.02fµT y=%0.02fµT z=%0.02fµT heading=%0.01f° temperature=%0.01f°C status=%u", x, y, z, heading, + temp, status); if (this->x_sensor_ != nullptr) this->x_sensor_->publish_state(x); @@ -114,6 +145,8 @@ void QMC5883LComponent::update() { this->z_sensor_->publish_state(z); if (this->heading_sensor_ != nullptr) this->heading_sensor_->publish_state(heading); + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temp); } bool QMC5883LComponent::read_byte_16_(uint8_t a_register, uint16_t *data) { diff --git a/esphome/components/qmc5883l/qmc5883l.h b/esphome/components/qmc5883l/qmc5883l.h index 15ef435ce5..b0c0af40d2 100644 --- a/esphome/components/qmc5883l/qmc5883l.h +++ b/esphome/components/qmc5883l/qmc5883l.h @@ -40,6 +40,7 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice { void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; } void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; } void set_heading_sensor(sensor::Sensor *heading_sensor) { heading_sensor_ = heading_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } protected: QMC5883LDatarate datarate_{QMC5883L_DATARATE_10_HZ}; @@ -49,6 +50,7 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *y_sensor_{nullptr}; sensor::Sensor *z_sensor_{nullptr}; sensor::Sensor *heading_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/qmc5883l/sensor.py b/esphome/components/qmc5883l/sensor.py index b819fecfe1..24e1019507 100644 --- a/esphome/components/qmc5883l/sensor.py +++ b/esphome/components/qmc5883l/sensor.py @@ -6,12 +6,15 @@ from esphome.const import ( CONF_FIELD_STRENGTH_X, CONF_FIELD_STRENGTH_Y, CONF_FIELD_STRENGTH_Z, + CONF_TEMPERATURE, CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, + DEVICE_CLASS_TEMPERATURE, ICON_MAGNET, STATE_CLASS_MEASUREMENT, UNIT_MICROTESLA, + UNIT_CELSIUS, UNIT_DEGREES, ICON_SCREEN_ROTATION, CONF_UPDATE_INTERVAL, @@ -79,6 +82,12 @@ heading_schema = sensor.sensor_schema( icon=ICON_SCREEN_ROTATION, accuracy_decimals=1, ) +temperature_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, +) CONFIG_SCHEMA = ( cv.Schema( @@ -95,6 +104,7 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema, cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema, cv.Optional(CONF_HEADING): heading_schema, + cv.Optional(CONF_TEMPERATURE): temperature_schema, } ) .extend(cv.polling_component_schema("60s")) @@ -131,3 +141,6 @@ async def to_code(config): if CONF_HEADING in config: sens = await sensor.new_sensor(config[CONF_HEADING]) cg.add(var.set_heading_sensor(sens)) + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) diff --git a/esphome/components/qr_code/qr_code.cpp b/esphome/components/qr_code/qr_code.cpp index aecf7628dc..b60e60a4b0 100644 --- a/esphome/components/qr_code/qr_code.cpp +++ b/esphome/components/qr_code/qr_code.cpp @@ -51,5 +51,17 @@ void QrCode::draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset, } } } + +uint8_t QrCode::get_size() { + if (this->needs_update_) { + this->generate_qr_code(); + this->needs_update_ = false; + } + + uint8_t size = qrcodegen_getSize(this->qr_); + + return size; +} + } // namespace qr_code } // namespace esphome diff --git a/esphome/components/qr_code/qr_code.h b/esphome/components/qr_code/qr_code.h index d88e0aa09a..ab4c587b6d 100644 --- a/esphome/components/qr_code/qr_code.h +++ b/esphome/components/qr_code/qr_code.h @@ -24,6 +24,8 @@ class QrCode : public Component { void generate_qr_code(); + uint8_t get_size(); + protected: std::string value_; qrcodegen_Ecc ecc_; diff --git a/esphome/components/qspi_amoled/__init__.py b/esphome/components/qspi_amoled/__init__.py new file mode 100644 index 0000000000..c58ce8a01e --- /dev/null +++ b/esphome/components/qspi_amoled/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@clydebarrow"] diff --git a/esphome/components/qspi_amoled/display.py b/esphome/components/qspi_amoled/display.py new file mode 100644 index 0000000000..84bf9553cb --- /dev/null +++ b/esphome/components/qspi_amoled/display.py @@ -0,0 +1,131 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import ( + spi, + display, +) +from esphome.const import ( + CONF_RESET_PIN, + CONF_ID, + CONF_DIMENSIONS, + CONF_WIDTH, + CONF_HEIGHT, + CONF_LAMBDA, + CONF_BRIGHTNESS, + CONF_ENABLE_PIN, + CONF_MODEL, + CONF_OFFSET_HEIGHT, + CONF_OFFSET_WIDTH, + CONF_INVERT_COLORS, + CONF_MIRROR_X, + CONF_MIRROR_Y, + CONF_SWAP_XY, + CONF_COLOR_ORDER, + CONF_TRANSFORM, +) + +DEPENDENCIES = ["spi"] + +qspi_amoled_ns = cg.esphome_ns.namespace("qspi_amoled") +QSPI_AMOLED = qspi_amoled_ns.class_( + "QspiAmoLed", display.Display, display.DisplayBuffer, cg.Component, spi.SPIDevice +) +ColorOrder = display.display_ns.enum("ColorMode") +Model = qspi_amoled_ns.enum("Model") + +MODELS = {"RM690B0": Model.RM690B0, "RM67162": Model.RM67162} + +COLOR_ORDERS = { + "RGB": ColorOrder.COLOR_ORDER_RGB, + "BGR": ColorOrder.COLOR_ORDER_BGR, +} +DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema + +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(QSPI_AMOLED), + cv.Required(CONF_MODEL): cv.enum(MODELS, upper=True), + cv.Required(CONF_DIMENSIONS): cv.Any( + cv.dimensions, + cv.Schema( + { + cv.Required(CONF_WIDTH): cv.int_, + cv.Required(CONF_HEIGHT): cv.int_, + cv.Optional(CONF_OFFSET_HEIGHT, default=0): cv.int_, + cv.Optional(CONF_OFFSET_WIDTH, default=0): cv.int_, + } + ), + ), + cv.Optional(CONF_TRANSFORM): cv.Schema( + { + cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, + cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, + } + ), + cv.Optional(CONF_COLOR_ORDER, default="RGB"): cv.enum( + COLOR_ORDERS, upper=True + ), + cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range( + 0, 0xFF, min_included=True, max_included=True + ), + } + ).extend( + spi.spi_device_schema( + cs_pin_required=False, + default_mode="MODE0", + default_data_rate=10e6, + quad=True, + ) + ) + ), + cv.only_with_esp_idf, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await display.register_display(var, config) + await spi.register_spi_device(var, config) + + cg.add(var.set_color_mode(config[CONF_COLOR_ORDER])) + cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS])) + cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) + cg.add(var.set_model(config[CONF_MODEL])) + if enable_pin := config.get(CONF_ENABLE_PIN): + enable = await cg.gpio_pin_expression(enable_pin) + cg.add(var.set_enable_pin(enable)) + + if reset_pin := config.get(CONF_RESET_PIN): + reset = await cg.gpio_pin_expression(reset_pin) + cg.add(var.set_reset_pin(reset)) + + if transform := config.get(CONF_TRANSFORM): + cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) + cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y])) + cg.add(var.set_swap_xy(transform[CONF_SWAP_XY])) + + if CONF_DIMENSIONS in config: + dimensions = config[CONF_DIMENSIONS] + if isinstance(dimensions, dict): + cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT])) + cg.add( + var.set_offsets( + dimensions[CONF_OFFSET_WIDTH], dimensions[CONF_OFFSET_HEIGHT] + ) + ) + else: + (width, height) = dimensions + cg.add(var.set_dimensions(width, height)) + + if lamb := config.get(CONF_LAMBDA): + lambda_ = await cg.process_lambda( + lamb, [(display.DisplayRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/qspi_amoled/qspi_amoled.cpp b/esphome/components/qspi_amoled/qspi_amoled.cpp new file mode 100644 index 0000000000..697989e861 --- /dev/null +++ b/esphome/components/qspi_amoled/qspi_amoled.cpp @@ -0,0 +1,165 @@ +#ifdef USE_ESP_IDF +#include "qspi_amoled.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace qspi_amoled { + +void QspiAmoLed::setup() { + esph_log_config(TAG, "Setting up QSPI_AMOLED"); + this->spi_setup(); + if (this->enable_pin_ != nullptr) { + this->enable_pin_->setup(); + this->enable_pin_->digital_write(true); + } + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->reset_pin_->digital_write(true); + delay(5); + this->reset_pin_->digital_write(false); + delay(5); + this->reset_pin_->digital_write(true); + } + this->set_timeout(120, [this] { this->write_command_(SLEEP_OUT); }); + this->set_timeout(240, [this] { this->write_init_sequence_(); }); +} + +void QspiAmoLed::update() { + this->do_update_(); + int w = this->x_high_ - this->x_low_ + 1; + int h = this->y_high_ - this->y_low_ + 1; + this->draw_pixels_at(this->x_low_, this->y_low_, w, h, this->buffer_, this->color_mode_, display::COLOR_BITNESS_565, + true, this->x_low_, this->y_low_, this->get_width_internal() - w - this->x_low_); + // invalidate watermarks + this->x_low_ = this->width_; + this->y_low_ = this->height_; + this->x_high_ = 0; + this->y_high_ = 0; +} + +void QspiAmoLed::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) { + return; + } + if (this->buffer_ == nullptr) + this->init_internal_(this->width_ * this->height_ * 2); + if (this->is_failed()) + return; + uint32_t pos = (y * this->width_) + x; + uint16_t new_color; + bool updated = false; + pos = pos * 2; + new_color = display::ColorUtil::color_to_565(color, display::ColorOrder::COLOR_ORDER_RGB); + if (this->buffer_[pos] != (uint8_t) (new_color >> 8)) { + this->buffer_[pos] = (uint8_t) (new_color >> 8); + updated = true; + } + pos = pos + 1; + new_color = new_color & 0xFF; + + if (this->buffer_[pos] != new_color) { + this->buffer_[pos] = new_color; + updated = true; + } + if (updated) { + // low and high watermark may speed up drawing from buffer + if (x < this->x_low_) + this->x_low_ = x; + if (y < this->y_low_) + this->y_low_ = y; + if (x > this->x_high_) + this->x_high_ = x; + if (y > this->y_high_) + this->y_high_ = y; + } +} + +void QspiAmoLed::reset_params_(bool ready) { + if (!ready && !this->is_ready()) + return; + this->write_command_(this->invert_colors_ ? INVERT_ON : INVERT_OFF); + // custom x/y transform and color order + uint8_t mad = this->color_mode_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB; + if (this->swap_xy_) + mad |= MADCTL_MV; + if (this->mirror_x_) + mad |= MADCTL_MX; + if (this->mirror_y_) + mad |= MADCTL_MY; + this->write_command_(MADCTL_CMD, &mad, 1); + this->write_command_(BRIGHTNESS, &this->brightness_, 1); +} + +void QspiAmoLed::write_init_sequence_() { + if (this->model_ == RM690B0) { + this->write_command_(PAGESEL, 0x20); + this->write_command_(MIPI, 0x0A); + this->write_command_(WRAM, 0x80); + this->write_command_(SWIRE1, 0x51); + this->write_command_(SWIRE2, 0x2E); + this->write_command_(PAGESEL, 0x00); + this->write_command_(0xC2, 0x00); + delay(10); + this->write_command_(TEON, 0x00); + } + this->write_command_(PIXFMT, 0x55); + this->write_command_(BRIGHTNESS, 0); + this->write_command_(DISPLAY_ON); + this->reset_params_(true); + this->setup_complete_ = true; + esph_log_config(TAG, "QSPI_AMOLED setup complete"); +} + +void QspiAmoLed::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { + uint8_t buf[4]; + x1 += this->offset_x_; + x2 += this->offset_x_; + y1 += this->offset_y_; + y2 += this->offset_y_; + put16_be(buf, x1); + put16_be(buf + 2, x2); + this->write_command_(CASET, buf, sizeof buf); + put16_be(buf, y1); + put16_be(buf + 2, y2); + this->write_command_(RASET, buf, sizeof buf); +} + +void QspiAmoLed::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { + if (!this->setup_complete_ || this->is_failed()) + return; + if (w <= 0 || h <= 0) + return; + if (bitness != display::COLOR_BITNESS_565 || order != this->color_mode_ || + big_endian != (this->bit_order_ == spi::BIT_ORDER_MSB_FIRST)) { + return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, + x_pad); + } + this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1); + this->enable(); + // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. + if (x_offset == 0 && x_pad == 0 && y_offset == 0) { + // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother + this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, ptr, w * h * 2, 4); + } else { + this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, nullptr, 0, 4); + auto stride = x_offset + w + x_pad; + for (int y = 0; y != h; y++) { + this->write_cmd_addr_data(0, 0, 0, 0, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4); + } + } + this->disable(); +} + +void QspiAmoLed::dump_config() { + ESP_LOGCONFIG("", "QSPI AMOLED"); + ESP_LOGCONFIG(TAG, " Height: %u", this->height_); + ESP_LOGCONFIG(TAG, " Width: %u", this->width_); + LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); +} + +} // namespace qspi_amoled +} // namespace esphome +#endif diff --git a/esphome/components/qspi_amoled/qspi_amoled.h b/esphome/components/qspi_amoled/qspi_amoled.h new file mode 100644 index 0000000000..28d243f548 --- /dev/null +++ b/esphome/components/qspi_amoled/qspi_amoled.h @@ -0,0 +1,165 @@ +// +// Created by Clyde Stubbs on 29/10/2023. +// +#pragma once + +#ifdef USE_ESP_IDF +#include "esphome/core/component.h" +#include "esphome/components/spi/spi.h" +#include "esphome/components/display/display.h" +#include "esphome/components/display/display_buffer.h" +#include "esphome/components/display/display_color_utils.h" +#include "esp_lcd_panel_ops.h" + +#include "esp_lcd_panel_rgb.h" + +namespace esphome { +namespace qspi_amoled { + +constexpr static const char *const TAG = "display.qspi_amoled"; +static const uint8_t SW_RESET_CMD = 0x01; +static const uint8_t SLEEP_OUT = 0x11; +static const uint8_t INVERT_OFF = 0x20; +static const uint8_t INVERT_ON = 0x21; +static const uint8_t ALL_ON = 0x23; +static const uint8_t WRAM = 0x24; +static const uint8_t MIPI = 0x26; +static const uint8_t DISPLAY_ON = 0x29; +static const uint8_t RASET = 0x2B; +static const uint8_t CASET = 0x2A; +static const uint8_t WDATA = 0x2C; +static const uint8_t TEON = 0x35; +static const uint8_t MADCTL_CMD = 0x36; +static const uint8_t PIXFMT = 0x3A; +static const uint8_t BRIGHTNESS = 0x51; +static const uint8_t SWIRE1 = 0x5A; +static const uint8_t SWIRE2 = 0x5B; +static const uint8_t PAGESEL = 0xFE; + +static const uint8_t MADCTL_MY = 0x80; ///< Bit 7 Bottom to top +static const uint8_t MADCTL_MX = 0x40; ///< Bit 6 Right to left +static const uint8_t MADCTL_MV = 0x20; ///< Bit 5 Reverse Mode +static const uint8_t MADCTL_RGB = 0x00; ///< Bit 3 Red-Green-Blue pixel order +static const uint8_t MADCTL_BGR = 0x08; ///< Bit 3 Blue-Green-Red pixel order + +// store a 16 bit value in a buffer, big endian. +static inline void put16_be(uint8_t *buf, uint16_t value) { + buf[0] = value >> 8; + buf[1] = value; +} + +enum Model { + RM690B0, + RM67162, +}; + +class QspiAmoLed : public display::DisplayBuffer, + public spi::SPIDevice { + public: + void set_model(Model model) { this->model_ = model; } + void update() override; + void setup() override; + display::ColorOrder get_color_mode() { return this->color_mode_; } + void set_color_mode(display::ColorOrder color_mode) { this->color_mode_ = color_mode; } + + void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } + void set_enable_pin(GPIOPin *enable_pin) { this->enable_pin_ = enable_pin; } + void set_width(uint16_t width) { this->width_ = width; } + void set_dimensions(uint16_t width, uint16_t height) { + this->width_ = width; + this->height_ = height; + } + int get_width() override { return this->width_; } + int get_height() override { return this->height_; } + void set_invert_colors(bool invert_colors) { + this->invert_colors_ = invert_colors; + this->reset_params_(); + } + void set_mirror_x(bool mirror_x) { + this->mirror_x_ = mirror_x; + this->reset_params_(); + } + void set_mirror_y(bool mirror_y) { + this->mirror_y_ = mirror_y; + this->reset_params_(); + } + void set_swap_xy(bool swap_xy) { + this->swap_xy_ = swap_xy; + this->reset_params_(); + } + void set_brightness(uint8_t brightness) { + this->brightness_ = brightness; + this->reset_params_(); + } + void set_offsets(int16_t offset_x, int16_t offset_y) { + this->offset_x_ = offset_x; + this->offset_y_ = offset_y; + } + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + void dump_config() override; + + int get_width_internal() override { return this->width_; } + int get_height_internal() override { return this->height_; } + bool can_proceed() override { return this->setup_complete_; } + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; + /** + * the RM67162 in quad SPI mode seems to work like this (not in the datasheet, this is deduced from the + * sample code.) + * + * Immediately after enabling /CS send 4 bytes in single-dataline SPI mode: + * 0: either 0x2 or 0x32. The first indicates that any subsequent data bytes after the initial 4 will be + * sent in 1-dataline SPI. The second indicates quad mode. + * 1: 0x00 + * 2: The command (register address) byte. + * 3: 0x00 + * + * This is followed by zero or more data bytes in either 1-wire or 4-wire mode, depending on the first byte. + * At the conclusion of the write, de-assert /CS. + * + * @param cmd + * @param bytes + * @param len + */ + void write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) { + this->enable(); + this->write_cmd_addr_data(8, 0x02, 24, cmd << 8, bytes, len); + this->disable(); + } + + void write_command_(uint8_t cmd, uint8_t data) { this->write_command_(cmd, &data, 1); } + void write_command_(uint8_t cmd) { this->write_command_(cmd, &cmd, 0); } + void reset_params_(bool ready = false); + void write_init_sequence_(); + void set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2); + + GPIOPin *reset_pin_{nullptr}; + GPIOPin *enable_pin_{nullptr}; + uint16_t x_low_{0}; + uint16_t y_low_{0}; + uint16_t x_high_{0}; + uint16_t y_high_{0}; + bool setup_complete_{}; + + bool invert_colors_{}; + display::ColorOrder color_mode_{display::COLOR_ORDER_BGR}; + size_t width_{}; + size_t height_{}; + int16_t offset_x_{0}; + int16_t offset_y_{0}; + bool swap_xy_{}; + bool mirror_x_{}; + bool mirror_y_{}; + uint8_t brightness_{0xD0}; + Model model_{RM690B0}; + + esp_lcd_panel_handle_t handle_{}; +}; + +} // namespace qspi_amoled +} // namespace esphome +#endif diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 3accd5038c..08652bbfc9 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -32,6 +32,7 @@ from esphome.const import ( CONF_MAGNITUDE, CONF_WAND_ID, CONF_LEVEL, + CONF_DELTA, ) from esphome.core import coroutine from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor @@ -633,6 +634,62 @@ async def magiquest_action(var, config, args): cg.add(var.set_magnitude(template_)) +# Microchip HCS301 KeeLoq OOK +( + KeeloqData, + KeeloqBinarySensor, + KeeloqTrigger, + KeeloqAction, + KeeloqDumper, +) = declare_protocol("Keeloq") +KEELOQ_SCHEMA = cv.Schema( + { + cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFFFFF)), + cv.Required(CONF_CODE): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFFFFFF)), + cv.Optional(CONF_COMMAND, default=0x10): cv.All( + cv.hex_int, + cv.Range(min=0, max=0x10), + ), + cv.Optional(CONF_LEVEL, default=False): cv.boolean, + } +) + + +@register_binary_sensor("keeloq", KeeloqBinarySensor, KEELOQ_SCHEMA) +def Keeloq_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + KeeloqData, + ("address", config[CONF_ADDRESS]), + ("command", config[CONF_COMMAND]), + ) + ) + ) + + +@register_trigger("keeloq", KeeloqTrigger, KeeloqData) +def keeloq_trigger(var, config): + pass + + +@register_dumper("keeloq", KeeloqDumper) +def keeloq_dumper(var, config): + pass + + +@register_action("keeloq", KeeloqAction, KEELOQ_SCHEMA) +async def keeloq_action(var, config, args): + template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint32) + cg.add(var.set_address(template_)) + template_ = await cg.templatable(config[CONF_CODE], args, cg.uint32) + cg.add(var.set_encrypted(template_)) + template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8) + cg.add(var.set_command(template_)) + template_ = await cg.templatable(config[CONF_LEVEL], args, bool) + cg.add(var.set_vlow(template_)) + + # NEC NECData, NECBinarySensor, NECTrigger, NECAction, NECDumper = declare_protocol("NEC") NEC_SCHEMA = cv.Schema( @@ -736,6 +793,7 @@ async def pioneer_action(var, config, args): PRONTO_SCHEMA = cv.Schema( { cv.Required(CONF_DATA): cv.string, + cv.Optional(CONF_DELTA, default=-1): cv.int_, } ) @@ -747,6 +805,7 @@ def pronto_binary_sensor(var, config): cg.StructInitializer( ProntoData, ("data", config[CONF_DATA]), + ("delta", config[CONF_DELTA]), ) ) ) diff --git a/esphome/components/remote_base/drayton_protocol.cpp b/esphome/components/remote_base/drayton_protocol.cpp index fb5f37b470..acfb7a0f16 100644 --- a/esphome/components/remote_base/drayton_protocol.cpp +++ b/esphome/components/remote_base/drayton_protocol.cpp @@ -13,7 +13,8 @@ static const uint8_t NBITS_SYNC = 4; static const uint8_t NBITS_ADDRESS = 16; static const uint8_t NBITS_CHANNEL = 5; static const uint8_t NBITS_COMMAND = 7; -static const uint8_t NBITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND; +static const uint8_t NDATABITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND; +static const uint8_t MIN_RX_SRC = (NDATABITS + NBITS_SYNC / 2); static const uint8_t CMD_ON = 0x41; static const uint8_t CMD_OFF = 0x02; @@ -116,7 +117,7 @@ void DraytonProtocol::encode(RemoteTransmitData *dst, const DraytonData &data) { ESP_LOGV(TAG, "Send Drayton: out_data %08" PRIx32, out_data); - for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) { + for (uint32_t mask = 1UL << (NDATABITS - 1); mask != 0; mask >>= 1) { if (out_data & mask) { dst->mark(BIT_TIME_US); dst->space(BIT_TIME_US); @@ -134,79 +135,96 @@ optional DraytonProtocol::decode(RemoteReceiveData src) { .command = 0, }; - if (src.size() < 45) { - return {}; - } + while (src.size() - src.get_index() >= MIN_RX_SRC) { + ESP_LOGVV(TAG, + "Decode Drayton: %" PRId32 ", %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 + " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 + " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 "", + src.size() - src.get_index(), src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), + src.peek(5), src.peek(6), src.peek(7), src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), + src.peek(13), src.peek(14), src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19)); - ESP_LOGVV(TAG, - "Decode Drayton: %" PRId32 ", %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 - " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 - " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 "", - src.size(), src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), - src.peek(7), src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), - src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19)); + // If first preamble item is a space, skip it + if (src.peek_space_at_least(1)) { + src.advance(1); + } - // If first preamble item is a space, skip it - if (src.peek_space_at_least(1)) { - src.advance(1); - } + // Look for sync pulse, after. If sucessful index points to space of sync symbol + while (src.size() - src.get_index() >= MIN_RX_SRC) { + ESP_LOGVV(TAG, "Decode Drayton: sync search %d, %" PRId32 " %" PRId32, src.size() - src.get_index(), src.peek(), + src.peek(1)); + if (src.peek_mark(2 * BIT_TIME_US) && + (src.peek_space(2 * BIT_TIME_US, 1) || src.peek_space(3 * BIT_TIME_US, 1))) { + src.advance(1); + ESP_LOGVV(TAG, "Decode Drayton: Found SYNC, - %d", src.get_index()); + break; + } else { + src.advance(2); + } + } - // Look for sync pulse, after. If sucessful index points to space of sync symbol - for (uint16_t preamble = 0; preamble <= NBITS_PREAMBLE * 2; preamble += 2) { - ESP_LOGVV(TAG, "Decode Drayton: preamble %d %" PRId32 " %" PRId32, preamble, src.peek(preamble), - src.peek(preamble + 1)); - if (src.peek_mark(2 * BIT_TIME_US, preamble) && - (src.peek_space(2 * BIT_TIME_US, preamble + 1) || src.peek_space(3 * BIT_TIME_US, preamble + 1))) { - src.advance(preamble + 1); + // No point continuing if not enough samples remaining to complete a packet + if (src.size() - src.get_index() < NDATABITS) { + ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %" PRIu32, src.get_index()); break; } - } - // Read data. Index points to space of sync symbol - // Extract first bit - // Checks next bit to leave index pointing correctly - uint32_t out_data = 0; - uint8_t bit = NBITS_ADDRESS + NBITS_COMMAND + NBITS_CHANNEL - 1; - if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { - out_data |= 0 << bit; - } else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) && - (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) { - out_data |= 1 << bit; - } else { - ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %" PRIu32, src.get_index()); - return {}; - } - - // Before/after each bit is read the index points to the transition at the start of the bit period or, - // if there is no transition at the start of the bit period, then the transition in the middle of - // the previous bit period. - while (--bit >= 1) { - ESP_LOGVV(TAG, "Decode Drayton: Data, %2d %08" PRIx32, bit, out_data); - if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) && - (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { + // Read data. Index points to space of sync symbol + // Extract first bit + // Checks next bit to leave index pointing correctly + uint32_t out_data = 0; + uint8_t bit = NDATABITS - 1; + ESP_LOGVV(TAG, "Decode Drayton: first bit %d %" PRId32 ", %" PRId32, src.peek(0), src.peek(1), src.peek(2)); + if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { out_data |= 0 << bit; - } else if ((src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) && + } else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) && (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) { out_data |= 1 << bit; } else { - ESP_LOGVV(TAG, "Decode Drayton: Fail 2, %2d %08" PRIx32, bit, out_data); - return {}; + ESP_LOGV(TAG, "Decode Drayton: Fail 2, - %d %d %d", src.peek(-1), src.peek(0), src.peek(1)); + continue; } - } - if (src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) { - out_data |= 0; - } else if (src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) { - out_data |= 1; - } - ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08" PRIx32, bit, out_data); - out.channel = (uint8_t) (out_data & 0x1F); - out_data >>= NBITS_CHANNEL; - out.command = (uint8_t) (out_data & 0x7F); - out_data >>= NBITS_COMMAND; - out.address = (uint16_t) (out_data & 0xFFFF); + // Before/after each bit is read the index points to the transition at the start of the bit period or, + // if there is no transition at the start of the bit period, then the transition in the middle of + // the previous bit period. + while (--bit >= 1) { + ESP_LOGVV(TAG, "Decode Drayton: Data, %2d %08" PRIx32, bit, out_data); + if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) && + (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { + out_data |= 0 << bit; + } else if ((src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) && + (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) { + out_data |= 1 << bit; + } else { + break; + } + } - return out; + if (bit > 0) { + ESP_LOGVV(TAG, "Decode Drayton: Fail 3, %d %" PRId32 " %" PRId32, src.peek(-1), src.peek(0), src.peek(1)); + continue; + } + + if (src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) { + out_data |= 0; + } else if (src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) { + out_data |= 1; + } else { + continue; + } + + ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data); + + out.channel = (uint8_t) (out_data & 0x1F); + out_data >>= NBITS_CHANNEL; + out.command = (uint8_t) (out_data & 0x7F); + out_data >>= NBITS_COMMAND; + out.address = (uint16_t) (out_data & 0xFFFF); + + return out; + } + return {}; } void DraytonProtocol::dump(const DraytonData &data) { ESP_LOGI(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address, diff --git a/esphome/components/remote_base/haier_protocol.h b/esphome/components/remote_base/haier_protocol.h index 6a1c4bea72..7a4ee640e8 100644 --- a/esphome/components/remote_base/haier_protocol.h +++ b/esphome/components/remote_base/haier_protocol.h @@ -26,12 +26,11 @@ DECLARE_REMOTE_PROTOCOL(Haier) template class HaierAction : public RemoteTransmitterActionBase { public: - TEMPLATABLE_VALUE(std::vector, data) + TEMPLATABLE_VALUE(std::vector, code) - void set_code(const std::vector &code) { data_ = code; } void encode(RemoteTransmitData *dst, Ts... x) override { HaierData data{}; - data.data = this->data_.value(x...); + data.data = this->code_.value(x...); HaierProtocol().encode(dst, data); } }; diff --git a/esphome/components/remote_base/keeloq_protocol.cpp b/esphome/components/remote_base/keeloq_protocol.cpp new file mode 100644 index 0000000000..09d9ea4f53 --- /dev/null +++ b/esphome/components/remote_base/keeloq_protocol.cpp @@ -0,0 +1,191 @@ +#include "keeloq_protocol.h" +#include "esphome/core/log.h" + +#include + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.keeloq"; + +static const uint32_t BIT_TIME_US = 380; +static const uint8_t NBITS_PREAMBLE = 12; +static const uint8_t NBITS_REPEAT = 1; +static const uint8_t NBITS_VLOW = 1; +static const uint8_t NBITS_SERIAL = 28; +static const uint8_t NBITS_BUTTONS = 4; +static const uint8_t NBITS_DISC = 12; +static const uint8_t NBITS_SYNC_CNT = 16; + +static const uint8_t NBITS_FIXED_DATA = NBITS_REPEAT + NBITS_VLOW + NBITS_BUTTONS + NBITS_SERIAL; +static const uint8_t NBITS_ENCRYPTED_DATA = NBITS_BUTTONS + NBITS_DISC + NBITS_SYNC_CNT; +static const uint8_t NBITS_DATA = NBITS_FIXED_DATA + NBITS_ENCRYPTED_DATA; + +/* +KeeLoq Protocol + +Coded using information from datasheet for Microchip HCS301 KeeLow Code Hopping Encoder + +Encoder - Hopping code is generated at random. + +Decoder - Hopping code is ignored and not checked when received. Serial number of +transmitter and nutton command is decoded. + +*/ + +void KeeloqProtocol::encode(RemoteTransmitData *dst, const KeeloqData &data) { + uint32_t out_data = 0x0; + + ESP_LOGD(TAG, "Send Keeloq: address=%07" PRIx32 " command=%03x encrypted=%08" PRIx32, data.address, data.command, + data.encrypted); + ESP_LOGV(TAG, "Send Keeloq: data bits (%d + %d)", NBITS_ENCRYPTED_DATA, NBITS_FIXED_DATA); + + // Preamble = '01' x 12 + for (uint8_t cnt = NBITS_PREAMBLE; cnt; cnt--) { + dst->space(BIT_TIME_US); + dst->mark(BIT_TIME_US); + } + + // Header = 10 bit space + dst->space(10 * BIT_TIME_US); + + // Encrypted field + out_data = data.encrypted; + + ESP_LOGV(TAG, "Send Keeloq: Encrypted data %04x", out_data); + + for (uint32_t mask = 1, cnt = 0; cnt < NBITS_ENCRYPTED_DATA; cnt++, mask <<= 1) { + if (out_data & mask) { + dst->mark(1 * BIT_TIME_US); + dst->space(2 * BIT_TIME_US); + } else { + dst->mark(2 * BIT_TIME_US); + dst->space(1 * BIT_TIME_US); + } + } + + // first 32 bits of fixed portion + out_data = (data.command & 0x0f); + out_data <<= NBITS_SERIAL; + out_data |= data.address; + ESP_LOGV(TAG, "Send Keeloq: Fixed data %04x", out_data); + + for (uint32_t mask = 1, cnt = 0; cnt < (NBITS_FIXED_DATA - 2); cnt++, mask <<= 1) { + if (out_data & mask) { + dst->mark(1 * BIT_TIME_US); + dst->space(2 * BIT_TIME_US); + } else { + dst->mark(2 * BIT_TIME_US); + dst->space(1 * BIT_TIME_US); + } + } + + // low battery flag + if (data.vlow) { + dst->mark(1 * BIT_TIME_US); + dst->space(2 * BIT_TIME_US); + } else { + dst->mark(2 * BIT_TIME_US); + dst->space(1 * BIT_TIME_US); + } + + // repeat flag - always sent as a '1' + dst->mark(1 * BIT_TIME_US); + dst->space(2 * BIT_TIME_US); + + // Guard time at end of packet + dst->space(39 * BIT_TIME_US); +} + +optional KeeloqProtocol::decode(RemoteReceiveData src) { + KeeloqData out{ + .encrypted = 0, + .address = 0, + .command = 0, + .repeat = false, + .vlow = false, + + }; + + if (src.size() != (NBITS_PREAMBLE + NBITS_DATA) * 2) { + return {}; + } + + ESP_LOGVV(TAG, "%2d: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(), src.peek(0), + src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7), src.peek(8), + src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), src.peek(15), + src.peek(16), src.peek(17), src.peek(18), src.peek(19)); + + // Check preamble bits + int8_t bit = NBITS_PREAMBLE - 1; + while (--bit >= 0) { + if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(BIT_TIME_US)) { + ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %d", bit + 1, src.peek()); + return {}; + } + } + if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(10 * BIT_TIME_US)) { + ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %d", bit + 1, src.peek()); + return {}; + } + + // Read encrypted bits + uint32_t out_data = 0; + for (bit = 0; bit < NBITS_ENCRYPTED_DATA; bit++) { + if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) { + out_data |= 0 << bit; + } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) { + out_data |= 1 << bit; + } else { + ESP_LOGV(TAG, "Decode KeeLoq: Fail 2, %d %d", src.get_index(), src.peek()); + return {}; + } + } + ESP_LOGVV(TAG, "Decode KeeLoq: Data, %d %08x", bit, out_data); + out.encrypted = out_data; + + // Read Serial Number and Button Status + out_data = 0; + for (bit = 0; bit < NBITS_SERIAL + NBITS_BUTTONS; bit++) { + if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) { + out_data |= 0 << bit; + } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) { + out_data |= 1 << bit; + } else { + ESP_LOGV(TAG, "Decode KeeLoq: Fail 3, %d %d", src.get_index(), src.peek()); + return {}; + } + } + ESP_LOGVV(TAG, "Decode KeeLoq: Data, %2d %08x", bit, out_data); + out.command = (out_data >> 28) & 0xf; + out.address = out_data & 0xfffffff; + + // Read Vlow bit + if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) { + out.vlow = false; + } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) { + out.vlow = true; + } else { + ESP_LOGV(TAG, "Decode KeeLoq: Fail 4, %08x", src.peek()); + return {}; + } + + // Read Repeat bit + if (src.expect_mark(2 * BIT_TIME_US) && src.peek_space_at_least(BIT_TIME_US)) { + out.repeat = false; + } else if (src.expect_mark(BIT_TIME_US) && src.peek_space_at_least(2 * BIT_TIME_US)) { + out.repeat = true; + } else { + ESP_LOGV(TAG, "Decode KeeLoq: Fail 5, %08x", src.peek()); + return {}; + } + + return out; +} + +void KeeloqProtocol::dump(const KeeloqData &data) { + ESP_LOGD(TAG, "Received Keeloq: address=0x%08" PRIx32 ", command=0x%02x", data.address, data.command); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/keeloq_protocol.h b/esphome/components/remote_base/keeloq_protocol.h new file mode 100644 index 0000000000..47125c151b --- /dev/null +++ b/esphome/components/remote_base/keeloq_protocol.h @@ -0,0 +1,53 @@ +#pragma once + +#include "esphome/core/component.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct KeeloqData { + uint32_t encrypted; // 32 bit encrypted field + uint32_t address; // 28 bit serial number + uint8_t command; // Button Status S2-S1-S0-S3 + bool repeat; // Repeated command bit + bool vlow; // Battery status bit + + bool operator==(const KeeloqData &rhs) const { + // Treat 0x10 as a special, wildcard button press + // This allows us to match on just the address if wanted. + if (address != rhs.address) { + return false; + } + return (rhs.command == 0x10 || command == rhs.command); + } +}; + +class KeeloqProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const KeeloqData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const KeeloqData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(Keeloq) + +template class KeeloqAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint32_t, address) + TEMPLATABLE_VALUE(uint32_t, encrypted) + TEMPLATABLE_VALUE(uint8_t, command) + TEMPLATABLE_VALUE(bool, vlow) + + void encode(RemoteTransmitData *dst, Ts... x) override { + KeeloqData data{}; + data.address = this->address_.value(x...); + data.encrypted = this->encrypted_.value(x...); + data.command = this->command_.value(x...); + data.vlow = this->vlow_.value(x...); + KeeloqProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index 4b6977e1a2..625af76235 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -49,13 +49,13 @@ bool ProntoData::operator==(const ProntoData &rhs) const { for (std::vector::size_type i = 0; i < data1.size() - 1; ++i) { int diff = data2[i] - data1[i]; diff *= diff; - if (diff > 9) + if (rhs.delta == -1 && diff > 9) return false; total_diff += diff; } - return total_diff <= data1.size() * 3; + return total_diff <= (rhs.delta == -1 ? data1.size() * 3 : rhs.delta); } // DO NOT EXPORT from this file @@ -222,21 +222,23 @@ optional ProntoProtocol::decode(RemoteReceiveData src) { prontodata += compensate_and_dump_sequence_(data, timebase); out.data = prontodata; + out.delta = -1; return out; } void ProntoProtocol::dump(const ProntoData &data) { - std::string first, rest; - if (data.data.size() < 230) { - first = data.data; - } else { - first = data.data.substr(0, 229); - rest = data.data.substr(230); - } - ESP_LOGI(TAG, "Received Pronto: data=%s", first.c_str()); - if (!rest.empty()) { - ESP_LOGI(TAG, "%s", rest.c_str()); + std::string rest; + + rest = data.data; + ESP_LOGI(TAG, "Received Pronto: data="); + while (true) { + ESP_LOGI(TAG, "%s", rest.substr(0, 230).c_str()); + if (rest.size() > 230) { + rest = rest.substr(230); + } else { + break; + } } } diff --git a/esphome/components/remote_base/pronto_protocol.h b/esphome/components/remote_base/pronto_protocol.h index 8b2163af12..e600834d1a 100644 --- a/esphome/components/remote_base/pronto_protocol.h +++ b/esphome/components/remote_base/pronto_protocol.h @@ -12,6 +12,7 @@ std::vector encode_pronto(const std::string &str); struct ProntoData { std::string data; + int delta; bool operator==(const ProntoData &rhs) const; }; @@ -40,10 +41,12 @@ DECLARE_REMOTE_PROTOCOL(Pronto) template class ProntoAction : public RemoteTransmitterActionBase { public: TEMPLATABLE_VALUE(std::string, data) + TEMPLATABLE_VALUE(int, delta) void encode(RemoteTransmitData *dst, Ts... x) override { ProntoData data{}; data.data = this->data_.value(x...); + data.delta = this->delta_.value(x...); ProntoProtocol().encode(dst, data); } }; diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index 095f95053f..f3e86aaab6 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -16,7 +16,7 @@ RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_b } void RemoteRMTChannel::config_rmt(rmt_config_t &rmt) { - if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) >= RMT_CHANNEL_MAX) { + if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) > RMT_CHANNEL_MAX) { this->mem_block_num_ = int(RMT_CHANNEL_MAX) - int(this->channel_); ESP_LOGW(TAG, "Not enough RMT memory blocks available, reduced to %i blocks.", this->mem_block_num_); } diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index 7440214b1c..a3631ffe27 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -226,6 +226,7 @@ void RotaryEncoderSensor::loop() { } this->store_.last_read = counter; this->publish_state(counter); + this->listeners_.call(counter); this->publish_initial_value_ = false; } } diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index deba3d952d..e88ee9152a 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -92,6 +92,8 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { this->on_anticlockwise_callback_.add(std::move(callback)); } + void register_listener(std::function listener) { this->listeners_.add(std::move(listener)); } + protected: InternalGPIOPin *pin_a_; InternalGPIOPin *pin_b_; @@ -102,8 +104,9 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { RotaryEncoderSensorStore store_{}; - CallbackManager on_clockwise_callback_; - CallbackManager on_anticlockwise_callback_; + CallbackManager on_clockwise_callback_{}; + CallbackManager on_anticlockwise_callback_{}; + CallbackManager listeners_{}; }; template class RotaryEncoderSetValueAction : public Action { diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index d027f48244..b262a068fb 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -74,12 +74,12 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: # The default/recommended arduino framework version # - https://github.com/earlephilhower/arduino-pico/releases # - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico -RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 6, 0) +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 7, 2) # The platformio/raspberrypi version to use for arduino frameworks # - https://github.com/platformio/platform-raspberrypi/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/raspberrypi -ARDUINO_PLATFORM_VERSION = cv.Version(1, 10, 0) +ARDUINO_PLATFORM_VERSION = cv.Version(1, 12, 0) def _arduino_check_versions(value): diff --git a/esphome/components/rpi_dpi_rgb/__init__.py b/esphome/components/rpi_dpi_rgb/__init__.py new file mode 100644 index 0000000000..c58ce8a01e --- /dev/null +++ b/esphome/components/rpi_dpi_rgb/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@clydebarrow"] diff --git a/esphome/components/rpi_dpi_rgb/display.py b/esphome/components/rpi_dpi_rgb/display.py new file mode 100644 index 0000000000..0cde16e0fb --- /dev/null +++ b/esphome/components/rpi_dpi_rgb/display.py @@ -0,0 +1,197 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import display +from esphome.const import ( + CONF_RESET_PIN, + CONF_DATA_PINS, + CONF_ID, + CONF_IGNORE_STRAPPING_WARNING, + CONF_DIMENSIONS, + CONF_WIDTH, + CONF_HEIGHT, + CONF_LAMBDA, + CONF_COLOR_ORDER, + CONF_RED, + CONF_GREEN, + CONF_BLUE, + CONF_NUMBER, + CONF_OFFSET_HEIGHT, + CONF_OFFSET_WIDTH, + CONF_INVERT_COLORS, +) +from esphome.components.esp32 import ( + only_on_variant, + const, +) + +DEPENDENCIES = ["esp32"] + +CONF_DE_PIN = "de_pin" +CONF_PCLK_PIN = "pclk_pin" +CONF_HSYNC_PIN = "hsync_pin" +CONF_VSYNC_PIN = "vsync_pin" + +CONF_HSYNC_FRONT_PORCH = "hsync_front_porch" +CONF_HSYNC_PULSE_WIDTH = "hsync_pulse_width" +CONF_HSYNC_BACK_PORCH = "hsync_back_porch" +CONF_VSYNC_FRONT_PORCH = "vsync_front_porch" +CONF_VSYNC_PULSE_WIDTH = "vsync_pulse_width" +CONF_VSYNC_BACK_PORCH = "vsync_back_porch" +CONF_PCLK_FREQUENCY = "pclk_frequency" +CONF_PCLK_INVERTED = "pclk_inverted" + +rpi_dpi_rgb_ns = cg.esphome_ns.namespace("rpi_dpi_rgb") +RPI_DPI_RGB = rpi_dpi_rgb_ns.class_("RpiDpiRgb", display.Display, cg.Component) +ColorOrder = display.display_ns.enum("ColorMode") + +COLOR_ORDERS = { + "RGB": ColorOrder.COLOR_ORDER_RGB, + "BGR": ColorOrder.COLOR_ORDER_BGR, +} +DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema + + +def data_pin_validate(value): + """ + It is safe to use strapping pins as RGB output data bits, as they are outputs only, + and not initialised until after boot. + """ + if not isinstance(value, dict): + try: + return DATA_PIN_SCHEMA( + {CONF_NUMBER: value, CONF_IGNORE_STRAPPING_WARNING: True} + ) + except cv.Invalid: + pass + return DATA_PIN_SCHEMA(value) + + +def data_pin_set(length): + return cv.All( + [data_pin_validate], + cv.Length(min=length, max=length, msg=f"Exactly {length} data pins required"), + ) + + +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(RPI_DPI_RGB), + cv.Required(CONF_DIMENSIONS): cv.Any( + cv.dimensions, + cv.Schema( + { + cv.Required(CONF_WIDTH): cv.int_, + cv.Required(CONF_HEIGHT): cv.int_, + cv.Optional(CONF_OFFSET_HEIGHT, default=0): cv.int_, + cv.Optional(CONF_OFFSET_WIDTH, default=0): cv.int_, + } + ), + ), + cv.Optional(CONF_PCLK_FREQUENCY, default="16MHz"): cv.All( + cv.frequency, cv.Range(min=4e6, max=30e6) + ), + cv.Optional(CONF_PCLK_INVERTED, default=True): cv.boolean, + cv.Required(CONF_DATA_PINS): cv.Any( + data_pin_set(16), + cv.Schema( + { + cv.Required(CONF_RED): data_pin_set(5), + cv.Required(CONF_GREEN): data_pin_set(6), + cv.Required(CONF_BLUE): data_pin_set(5), + } + ), + ), + cv.Optional(CONF_COLOR_ORDER): cv.one_of( + *COLOR_ORDERS.keys(), upper=True + ), + cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, + cv.Required(CONF_DE_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_PCLK_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_HSYNC_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_HSYNC_PULSE_WIDTH, default=10): cv.int_, + cv.Optional(CONF_HSYNC_BACK_PORCH, default=10): cv.int_, + cv.Optional(CONF_HSYNC_FRONT_PORCH, default=20): cv.int_, + cv.Optional(CONF_VSYNC_PULSE_WIDTH, default=10): cv.int_, + cv.Optional(CONF_VSYNC_BACK_PORCH, default=10): cv.int_, + cv.Optional(CONF_VSYNC_FRONT_PORCH, default=10): cv.int_, + } + ) + ), + only_on_variant(supported=[const.VARIANT_ESP32S3]), + cv.only_with_esp_idf, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await display.register_display(var, config) + + cg.add(var.set_color_mode(COLOR_ORDERS[config[CONF_COLOR_ORDER]])) + cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS])) + cg.add(var.set_hsync_pulse_width(config[CONF_HSYNC_PULSE_WIDTH])) + cg.add(var.set_hsync_back_porch(config[CONF_HSYNC_BACK_PORCH])) + cg.add(var.set_hsync_front_porch(config[CONF_HSYNC_FRONT_PORCH])) + cg.add(var.set_vsync_pulse_width(config[CONF_VSYNC_PULSE_WIDTH])) + cg.add(var.set_vsync_back_porch(config[CONF_VSYNC_BACK_PORCH])) + cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH])) + cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED])) + cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY])) + index = 0 + dpins = [] + if CONF_RED in config[CONF_DATA_PINS]: + red_pins = config[CONF_DATA_PINS][CONF_RED] + green_pins = config[CONF_DATA_PINS][CONF_GREEN] + blue_pins = config[CONF_DATA_PINS][CONF_BLUE] + if config[CONF_COLOR_ORDER] == "BGR": + dpins.extend(red_pins) + dpins.extend(green_pins) + dpins.extend(blue_pins) + else: + dpins.extend(blue_pins) + dpins.extend(green_pins) + dpins.extend(red_pins) + # swap bytes to match big-endian format + dpins = dpins[8:16] + dpins[0:8] + else: + dpins = config[CONF_DATA_PINS] + for pin in dpins: + data_pin = await cg.gpio_pin_expression(pin) + cg.add(var.add_data_pin(data_pin, index)) + index += 1 + + if reset_pin := config.get(CONF_RESET_PIN): + reset = await cg.gpio_pin_expression(reset_pin) + cg.add(var.set_reset_pin(reset)) + + if CONF_DIMENSIONS in config: + dimensions = config[CONF_DIMENSIONS] + if isinstance(dimensions, dict): + cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT])) + cg.add( + var.set_offsets( + dimensions[CONF_OFFSET_WIDTH], dimensions[CONF_OFFSET_HEIGHT] + ) + ) + else: + (width, height) = dimensions + cg.add(var.set_dimensions(width, height)) + + if lamb := config.get(CONF_LAMBDA): + lambda_ = await cg.process_lambda( + lamb, [(display.DisplayRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) + + pin = await cg.gpio_pin_expression(config[CONF_DE_PIN]) + cg.add(var.set_de_pin(pin)) + pin = await cg.gpio_pin_expression(config[CONF_PCLK_PIN]) + cg.add(var.set_pclk_pin(pin)) + pin = await cg.gpio_pin_expression(config[CONF_HSYNC_PIN]) + cg.add(var.set_hsync_pin(pin)) + pin = await cg.gpio_pin_expression(config[CONF_VSYNC_PIN]) + cg.add(var.set_vsync_pin(pin)) diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp new file mode 100644 index 0000000000..2ffdb3272a --- /dev/null +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp @@ -0,0 +1,116 @@ +#ifdef USE_ESP32_VARIANT_ESP32S3 +#include "rpi_dpi_rgb.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace rpi_dpi_rgb { + +void RpiDpiRgb::setup() { + esph_log_config(TAG, "Setting up RPI_DPI_RGB"); + esp_lcd_rgb_panel_config_t config{}; + config.flags.fb_in_psram = 1; + config.timings.h_res = this->width_; + config.timings.v_res = this->height_; + config.timings.hsync_pulse_width = this->hsync_pulse_width_; + config.timings.hsync_back_porch = this->hsync_back_porch_; + config.timings.hsync_front_porch = this->hsync_front_porch_; + config.timings.vsync_pulse_width = this->vsync_pulse_width_; + config.timings.vsync_back_porch = this->vsync_back_porch_; + config.timings.vsync_front_porch = this->vsync_front_porch_; + config.timings.flags.pclk_active_neg = this->pclk_inverted_; + config.timings.pclk_hz = this->pclk_frequency_; + config.clk_src = LCD_CLK_SRC_PLL160M; + config.sram_trans_align = 64; + config.psram_trans_align = 64; + size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]); + for (size_t i = 0; i != data_pin_count; i++) { + config.data_gpio_nums[i] = this->data_pins_[i]->get_pin(); + } + config.data_width = data_pin_count; + config.disp_gpio_num = -1; + config.hsync_gpio_num = this->hsync_pin_->get_pin(); + config.vsync_gpio_num = this->vsync_pin_->get_pin(); + config.de_gpio_num = this->de_pin_->get_pin(); + config.pclk_gpio_num = this->pclk_pin_->get_pin(); + esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_); + if (err != ESP_OK) { + esph_log_e(TAG, "lcd_new_rgb_panel failed: %s", esp_err_to_name(err)); + } + ESP_ERROR_CHECK(esp_lcd_panel_reset(this->handle_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(this->handle_)); + esph_log_config(TAG, "RPI_DPI_RGB setup complete"); +} + +void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { + if (w <= 0 || h <= 0) + return; + // if color mapping is required, pass the buck. + // note that endianness is not considered here - it is assumed to match! + if (bitness != display::COLOR_BITNESS_565) { + return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, + x_pad); + } + x_start += this->offset_x_; + y_start += this->offset_y_; + esp_err_t err; + // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. + if (x_offset == 0 && x_pad == 0 && y_offset == 0) { + // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother + err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr); + } else { + // draw line by line + auto stride = x_offset + w + x_pad; + for (int y = 0; y != h; y++) { + err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y + y_start, x_start + w, y + y_start + 1, + ptr + ((y + y_offset) * stride + x_offset) * 2); + if (err != ESP_OK) + break; + } + } + if (err != ESP_OK) + esph_log_e(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err)); +} + +void RpiDpiRgb::draw_pixel_at(int x, int y, Color color) { + if (!this->get_clipping().inside(x, y)) + return; // NOLINT + + switch (this->rotation_) { + case display::DISPLAY_ROTATION_0_DEGREES: + break; + case display::DISPLAY_ROTATION_90_DEGREES: + std::swap(x, y); + x = this->width_ - x - 1; + break; + case display::DISPLAY_ROTATION_180_DEGREES: + x = this->width_ - x - 1; + y = this->height_ - y - 1; + break; + case display::DISPLAY_ROTATION_270_DEGREES: + std::swap(x, y); + y = this->height_ - y - 1; + break; + } + auto pixel = convert_big_endian(display::ColorUtil::color_to_565(color)); + + this->draw_pixels_at(x, y, 1, 1, (const uint8_t *) &pixel, display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, true, + 0, 0, 0); + App.feed_wdt(); +} + +void RpiDpiRgb::dump_config() { + ESP_LOGCONFIG("", "RPI_DPI_RGB LCD"); + ESP_LOGCONFIG(TAG, " Height: %u", this->height_); + ESP_LOGCONFIG(TAG, " Width: %u", this->width_); + LOG_PIN(" DE Pin: ", this->de_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]); + for (size_t i = 0; i != data_pin_count; i++) + ESP_LOGCONFIG(TAG, " Data pin %d: %s", i, (this->data_pins_[i])->dump_summary().c_str()); +} + +} // namespace rpi_dpi_rgb +} // namespace esphome + +#endif // USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h new file mode 100644 index 0000000000..0319b46391 --- /dev/null +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h @@ -0,0 +1,92 @@ +// +// Created by Clyde Stubbs on 29/10/2023. +// +#pragma once + +// only applicable on ESP32-S3 +#ifdef USE_ESP32_VARIANT_ESP32S3 +#include "esphome/core/component.h" +#include "esphome/core/gpio.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/components/display/display.h" +#include "esp_lcd_panel_ops.h" + +#include "esp_lcd_panel_rgb.h" + +namespace esphome { +namespace rpi_dpi_rgb { + +constexpr static const char *const TAG = "rpi_dpi_rgb"; + +class RpiDpiRgb : public display::Display { + public: + void update() override { this->do_update_(); } + void setup() override; + void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; + void draw_pixel_at(int x, int y, Color color) override; + + display::ColorOrder get_color_mode() { return this->color_mode_; } + void set_color_mode(display::ColorOrder color_mode) { this->color_mode_ = color_mode; } + void set_invert_colors(bool invert_colors) { this->invert_colors_ = invert_colors; } + + void add_data_pin(InternalGPIOPin *data_pin, size_t index) { this->data_pins_[index] = data_pin; }; + void set_de_pin(InternalGPIOPin *de_pin) { this->de_pin_ = de_pin; } + void set_pclk_pin(InternalGPIOPin *pclk_pin) { this->pclk_pin_ = pclk_pin; } + void set_vsync_pin(InternalGPIOPin *vsync_pin) { this->vsync_pin_ = vsync_pin; } + void set_hsync_pin(InternalGPIOPin *hsync_pin) { this->hsync_pin_ = hsync_pin; } + void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } + void set_width(uint16_t width) { this->width_ = width; } + void set_dimensions(uint16_t width, uint16_t height) { + this->width_ = width; + this->height_ = height; + } + int get_width() override { return this->width_; } + int get_height() override { return this->height_; } + void set_hsync_back_porch(uint16_t hsync_back_porch) { this->hsync_back_porch_ = hsync_back_porch; } + void set_hsync_front_porch(uint16_t hsync_front_porch) { this->hsync_front_porch_ = hsync_front_porch; } + void set_hsync_pulse_width(uint16_t hsync_pulse_width) { this->hsync_pulse_width_ = hsync_pulse_width; } + void set_vsync_pulse_width(uint16_t vsync_pulse_width) { this->vsync_pulse_width_ = vsync_pulse_width; } + void set_vsync_back_porch(uint16_t vsync_back_porch) { this->vsync_back_porch_ = vsync_back_porch; } + void set_vsync_front_porch(uint16_t vsync_front_porch) { this->vsync_front_porch_ = vsync_front_porch; } + void set_pclk_frequency(uint32_t pclk_frequency) { this->pclk_frequency_ = pclk_frequency; } + void set_pclk_inverted(bool inverted) { this->pclk_inverted_ = inverted; } + void set_offsets(int16_t offset_x, int16_t offset_y) { + this->offset_x_ = offset_x; + this->offset_y_ = offset_y; + } + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + void dump_config() override; + + protected: + int get_width_internal() override { return this->width_; } + int get_height_internal() override { return this->height_; } + InternalGPIOPin *de_pin_{nullptr}; + InternalGPIOPin *pclk_pin_{nullptr}; + InternalGPIOPin *hsync_pin_{nullptr}; + InternalGPIOPin *vsync_pin_{nullptr}; + GPIOPin *reset_pin_{nullptr}; + InternalGPIOPin *data_pins_[16] = {}; + uint16_t hsync_front_porch_ = 8; + uint16_t hsync_pulse_width_ = 4; + uint16_t hsync_back_porch_ = 8; + uint16_t vsync_front_porch_ = 8; + uint16_t vsync_pulse_width_ = 4; + uint16_t vsync_back_porch_ = 8; + uint32_t pclk_frequency_ = 16 * 1000 * 1000; + bool pclk_inverted_{true}; + + bool invert_colors_{}; + display::ColorOrder color_mode_{display::COLOR_ORDER_BGR}; + size_t width_{}; + size_t height_{}; + int16_t offset_x_{0}; + int16_t offset_y_{0}; + + esp_lcd_panel_handle_t handle_{}; +}; + +} // namespace rpi_dpi_rgb +} // namespace esphome +#endif // USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/rtl87xx/boards.py b/esphome/components/rtl87xx/boards.py index 6c29467f6e..e737767a56 100644 --- a/esphome/components/rtl87xx/boards.py +++ b/esphome/components/rtl87xx/boards.py @@ -36,6 +36,10 @@ RTL87XX_BOARDS = { "name": "T103_V1.0", "family": FAMILY_RTL8710B, }, + "t112-v1.1": { + "name": "T112_V1.1", + "family": FAMILY_RTL8710B, + }, "wr1": { "name": "WR1 Wi-Fi Module", "family": FAMILY_RTL8710B, @@ -125,7 +129,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -136,9 +139,7 @@ RTL87XX_BOARD_PINS = { "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 22, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -180,11 +181,9 @@ RTL87XX_BOARD_PINS = { "SERIAL2_RTS": 20, "SERIAL2_RX": 15, "SERIAL2_TX": 16, - "CS0": 15, "CTS1": 4, "CTS2": 19, "MISO0": 20, - "MOSI0": 19, "PA00": 0, "PA0": 0, "PA01": 1, @@ -203,23 +202,15 @@ RTL87XX_BOARD_PINS = { "PA18": 18, "PA19": 19, "PA20": 20, - "PWM0": 0, "PWM1": 1, - "PWM2": 14, - "PWM3": 3, - "PWM4": 16, "PWM5": 17, "PWM6": 18, - "PWM7": 13, "RTS2": 20, "RX0": 13, - "RX1": 0, "RX2": 15, - "SCK0": 3, "SCL0": 19, "SDA0": 3, "TX0": 14, - "TX1": 1, "TX2": 16, "D0": 17, "D1": 18, @@ -294,7 +285,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -305,9 +295,7 @@ RTL87XX_BOARD_PINS = { "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -390,7 +378,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -401,9 +388,7 @@ RTL87XX_BOARD_PINS = { "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -485,7 +470,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -496,9 +480,7 @@ RTL87XX_BOARD_PINS = { "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -560,7 +542,6 @@ RTL87XX_BOARD_PINS = { "CTS0": 10, "CTS1": 4, "CTS2": 19, - "MISO0": 20, "MOSI0": 19, "PA00": 0, "PA0": 0, @@ -591,23 +572,13 @@ RTL87XX_BOARD_PINS = { "PA20": 20, "PA23": 23, "PWM0": 20, - "PWM1": 12, - "PWM2": 14, - "PWM3": 15, - "PWM4": 16, "PWM5": 17, "PWM6": 18, "PWM7": 23, "RTS0": 9, "RTS2": 20, - "RX0": 13, - "RX1": 2, "RX2": 15, "SCK0": 16, - "SCL0": 19, - "SDA0": 20, - "TX0": 14, - "TX1": 3, "TX2": 16, "D0": 0, "D1": 1, @@ -652,7 +623,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 14, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -720,7 +690,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -731,9 +700,7 @@ RTL87XX_BOARD_PINS = { "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -751,6 +718,75 @@ RTL87XX_BOARD_PINS = { "A0": 19, "A1": 41, }, + "t112-v1.1": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 29, + "WIRE0_SCL_1": 22, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 30, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL1": 18, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 29, + "D1": 19, + "D2": 15, + "D3": 14, + "D4": 0, + "D5": 5, + "D6": 18, + "D7": 12, + "D8": 23, + "D9": 22, + "D10": 30, + "A0": 19, + }, "wr1": { "SPI0_CS": 19, "SPI0_MISO": 22, @@ -793,7 +829,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 14, "PWM1": 15, "PWM2": 0, "PWM4": 29, @@ -803,9 +838,7 @@ RTL87XX_BOARD_PINS = { "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 22, "SCL1": 18, - "SDA0": 19, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -863,7 +896,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 14, "PWM1": 15, "PWM3": 12, "PWM4": 29, @@ -873,9 +905,7 @@ RTL87XX_BOARD_PINS = { "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 22, "SCL1": 18, - "SDA0": 19, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -915,7 +945,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 14, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -969,7 +998,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 14, "PWM1": 15, "PWM3": 12, "PWM4": 29, @@ -979,7 +1007,6 @@ RTL87XX_BOARD_PINS = { "SCK1": 18, "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -1083,7 +1110,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -1094,9 +1120,7 @@ RTL87XX_BOARD_PINS = { "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -1157,7 +1181,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -1168,9 +1191,7 @@ RTL87XX_BOARD_PINS = { "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 22, "SCL1": 18, - "SDA0": 19, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -1231,7 +1252,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -1242,9 +1262,7 @@ RTL87XX_BOARD_PINS = { "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -1305,7 +1323,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -1316,9 +1333,7 @@ RTL87XX_BOARD_PINS = { "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 22, "SCL1": 18, - "SDA0": 19, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -1359,7 +1374,6 @@ RTL87XX_BOARD_PINS = { "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, diff --git a/esphome/components/rtttl/__init__.py b/esphome/components/rtttl/__init__.py index 6163129529..10f1313408 100644 --- a/esphome/components/rtttl/__init__.py +++ b/esphome/components/rtttl/__init__.py @@ -12,6 +12,7 @@ from esphome.const import ( CONF_PLATFORM, CONF_TRIGGER_ID, CONF_SPEAKER, + CONF_GAIN, ) _LOGGER = logging.getLogger(__name__) @@ -38,6 +39,7 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(CONF_ID): cv.declare_id(Rtttl), cv.Optional(CONF_OUTPUT): cv.use_id(FloatOutput), cv.Optional(CONF_SPEAKER): cv.use_id(Speaker), + cv.Optional(CONF_GAIN, default="0.6"): cv.percentage, cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -98,6 +100,8 @@ async def to_code(config): out = await cg.get_variable(config[CONF_SPEAKER]) cg.add(var.set_speaker(out)) + cg.add(var.set_gain(config[CONF_GAIN])) + for conf in config.get(CONF_ON_FINISHED_PLAYBACK, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index 199a373785..0bdf65b7bd 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -16,7 +16,7 @@ static const uint16_t NOTES[] = {0, 262, 277, 294, 311, 330, 349, 370, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951}; -static const uint16_t I2S_SPEED = 1600; +static const uint16_t I2S_SPEED = 1000; #undef HALF_PI static const double HALF_PI = 1.5707963267948966192313216916398; @@ -136,7 +136,7 @@ void Rtttl::loop() { if (this->samples_per_wave_ != 0 && this->samples_sent_ >= this->samples_gap_) { // Play note// rem = ((this->samples_sent_ << 10) % this->samples_per_wave_) * (360.0 / this->samples_per_wave_); - int16_t val = 8192 * sin(deg2rad(rem)); + int16_t val = (49152 * this->gain_) * sin(deg2rad(rem)); sample[x].left = val; sample[x].right = val; @@ -269,7 +269,7 @@ void Rtttl::loop() { } if (this->output_freq_ != 0) { this->output_->update_frequency(this->output_freq_); - this->output_->set_level(0.5); + this->output_->set_level(this->gain_); } else { this->output_->set_level(0.0); } @@ -278,18 +278,23 @@ void Rtttl::loop() { #ifdef USE_SPEAKER if (this->speaker_ != nullptr) { this->samples_sent_ = 0; - this->samples_count_ = (this->sample_rate_ * this->note_duration_) / I2S_SPEED; - // Convert from frequency in Hz to high and low samples in fixed point + this->samples_gap_ = 0; + this->samples_per_wave_ = 0; + this->samples_count_ = (this->sample_rate_ * this->note_duration_) / 1600; //(ms); + if (need_note_gap) { + this->samples_gap_ = (this->sample_rate_ * DOUBLE_NOTE_GAP_MS) / 1600; //(ms); + } if (this->output_freq_ != 0) { this->samples_per_wave_ = (this->sample_rate_ << 10) / this->output_freq_; - } else { - this->samples_per_wave_ = 0; - } - if (need_note_gap) { - this->samples_gap_ = (this->sample_rate_ * DOUBLE_NOTE_GAP_MS) / I2S_SPEED; - } else { - this->samples_gap_ = 0; + + // make sure there is enough samples to add a full last sinus. + uint16_t division = ((this->samples_count_ << 10) / this->samples_per_wave_) + 1; + uint16_t x = this->samples_count_; + this->samples_count_ = (division * this->samples_per_wave_); + ESP_LOGD(TAG, "play time old: %d div: %d new: %d %d", x, division, this->samples_count_, this->samples_per_wave_); + this->samples_count_ = this->samples_count_ >> 10; } + // Convert from frequency in Hz to high and low samples in fixed point } #endif diff --git a/esphome/components/rtttl/rtttl.h b/esphome/components/rtttl/rtttl.h index e09b0265be..bf089ce980 100644 --- a/esphome/components/rtttl/rtttl.h +++ b/esphome/components/rtttl/rtttl.h @@ -15,7 +15,7 @@ namespace esphome { namespace rtttl { #ifdef USE_SPEAKER -static const size_t SAMPLE_BUFFER_SIZE = 256; +static const size_t SAMPLE_BUFFER_SIZE = 512; struct SpeakerSample { int16_t left{0}; @@ -31,6 +31,13 @@ class Rtttl : public Component { #ifdef USE_SPEAKER void set_speaker(speaker::Speaker *speaker) { this->speaker_ = speaker; } #endif + void set_gain(float gain) { + if (gain < 0.1f) + gain = 0.1f; + if (gain > 1.0f) + gain = 1.0f; + this->gain_ = gain; + } void play(std::string rtttl); void stop(); void dump_config() override; @@ -60,6 +67,7 @@ class Rtttl : public Component { uint16_t note_duration_; uint32_t output_freq_; + float gain_{0.6f}; #ifdef USE_OUTPUT output::FloatOutput *output_; @@ -68,13 +76,13 @@ class Rtttl : public Component { void play_output_(); #ifdef USE_SPEAKER - speaker::Speaker *speaker_; - void play_speaker_(); + speaker::Speaker *speaker_{nullptr}; int sample_rate_{16000}; int samples_per_wave_{0}; int samples_sent_{0}; int samples_count_{0}; int samples_gap_{0}; + #endif CallbackManager on_finished_playback_callback_; diff --git a/esphome/components/seeed_mr24hpc1/__init__.py b/esphome/components/seeed_mr24hpc1/__init__.py new file mode 100644 index 0000000000..52b971e7e4 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/__init__.py @@ -0,0 +1,51 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID + +DEPENDENCIES = ["uart"] +# is the code owner of the relevant code base +CODEOWNERS = ["@limengdu"] +# The current component or platform can be configured or defined multiple times in the same configuration file. +MULTI_CONF = True + +# This line of code creates a new namespace called mr24hpc1_ns. +# This namespace will be used as a prefix for all classes, functions and variables associated with the mr24hpc1_ns component, ensuring that they do not conflict with the names of other components. +mr24hpc1_ns = cg.esphome_ns.namespace("seeed_mr24hpc1") +# This MR24HPC1Component class will be a periodically polled UART device +MR24HPC1Component = mr24hpc1_ns.class_( + "MR24HPC1Component", cg.Component, uart.UARTDevice +) + +CONF_MR24HPC1_ID = "mr24hpc1_id" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MR24HPC1Component), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +# A verification mode was created to verify the configuration parameters of a UART device named "seeed_mr24hpc1". +# This authentication mode requires that the device must have transmit and receive functionality, a parity mode of "NONE", and a stop bit of one. +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "seeed_mr24hpc1", + require_tx=True, + require_rx=True, + parity="NONE", + stop_bits=1, +) + + +# The async def keyword is used to define a concurrent function. +# Concurrent functions are special functions designed to work with Python's asyncio library to support asynchronous I/O operations. +async def to_code(config): + # This line of code creates a new Pvariable (a Python object representing a C++ variable) with the variable's ID taken from the configuration. + var = cg.new_Pvariable(config[CONF_ID]) + # This line of code registers the newly created Pvariable as a component so that ESPHome can manage it at runtime. + await cg.register_component(var, config) + # This line of code registers the newly created Pvariable as a device. + await uart.register_uart_device(var, config) diff --git a/esphome/components/seeed_mr24hpc1/binary_sensor.py b/esphome/components/seeed_mr24hpc1/binary_sensor.py new file mode 100644 index 0000000000..e3e54d03f9 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/binary_sensor.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_OCCUPANCY, +) +from . import CONF_MR24HPC1_ID, MR24HPC1Component + +CONF_HAS_TARGET = "has_target" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY, icon="mdi:motion-sensor" + ), +} + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if has_target_config := config.get(CONF_HAS_TARGET): + sens = await binary_sensor.new_binary_sensor(has_target_config) + cg.add(mr24hpc1_component.set_has_target_binary_sensor(sens)) diff --git a/esphome/components/seeed_mr24hpc1/button/__init__.py b/esphome/components/seeed_mr24hpc1/button/__init__.py new file mode 100644 index 0000000000..0a0e7a1865 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/button/__init__.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +from esphome.components import button +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_RESTART, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART_ALERT, +) +from .. import CONF_MR24HPC1_ID, MR24HPC1Component, mr24hpc1_ns + +RestartButton = mr24hpc1_ns.class_("RestartButton", button.Button) +CustomSetEndButton = mr24hpc1_ns.class_("CustomSetEndButton", button.Button) + +CONF_RESTART = "restart" +CONF_CUSTOM_SET_END = "custom_set_end" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_RESTART): button.button_schema( + RestartButton, + device_class=DEVICE_CLASS_RESTART, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_RESTART_ALERT, + ), + cv.Optional(CONF_CUSTOM_SET_END): button.button_schema( + CustomSetEndButton, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:cog", + ), +} + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if restart_config := config.get(CONF_RESTART): + b = await button.new_button(restart_config) + await cg.register_parented(b, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_restart_button(b)) + if custom_set_end_config := config.get(CONF_CUSTOM_SET_END): + b = await button.new_button(custom_set_end_config) + await cg.register_parented(b, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_custom_set_end_button(b)) diff --git a/esphome/components/seeed_mr24hpc1/button/custom_mode_end_button.cpp b/esphome/components/seeed_mr24hpc1/button/custom_mode_end_button.cpp new file mode 100644 index 0000000000..0ae8889247 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/button/custom_mode_end_button.cpp @@ -0,0 +1,9 @@ +#include "custom_mode_end_button.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void CustomSetEndButton::press_action() { this->parent_->set_custom_end_mode(); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/button/custom_mode_end_button.h b/esphome/components/seeed_mr24hpc1/button/custom_mode_end_button.h new file mode 100644 index 0000000000..a1701d8581 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/button/custom_mode_end_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class CustomSetEndButton : public button::Button, public Parented { + public: + CustomSetEndButton() = default; + + protected: + void press_action() override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/button/restart_button.cpp b/esphome/components/seeed_mr24hpc1/button/restart_button.cpp new file mode 100644 index 0000000000..6c4a070d1c --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/button/restart_button.cpp @@ -0,0 +1,9 @@ +#include "restart_button.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void RestartButton::press_action() { this->parent_->set_restart(); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/button/restart_button.h b/esphome/components/seeed_mr24hpc1/button/restart_button.h new file mode 100644 index 0000000000..8a2ec2087c --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/button/restart_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class RestartButton : public button::Button, public Parented { + public: + RestartButton() = default; + + protected: + void press_action() override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/__init__.py b/esphome/components/seeed_mr24hpc1/number/__init__.py new file mode 100644 index 0000000000..d9dfcb19a5 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/__init__.py @@ -0,0 +1,132 @@ +import esphome.codegen as cg +from esphome.components import number +import esphome.config_validation as cv +from esphome.const import ( + ENTITY_CATEGORY_CONFIG, +) +from .. import CONF_MR24HPC1_ID, MR24HPC1Component, mr24hpc1_ns + +SensitivityNumber = mr24hpc1_ns.class_("SensitivityNumber", number.Number) +CustomModeNumber = mr24hpc1_ns.class_("CustomModeNumber", number.Number) +ExistenceThresholdNumber = mr24hpc1_ns.class_("ExistenceThresholdNumber", number.Number) +MotionThresholdNumber = mr24hpc1_ns.class_("MotionThresholdNumber", number.Number) +MotionTriggerTimeNumber = mr24hpc1_ns.class_("MotionTriggerTimeNumber", number.Number) +MotionToRestTimeNumber = mr24hpc1_ns.class_("MotionToRestTimeNumber", number.Number) +CustomUnmanTimeNumber = mr24hpc1_ns.class_("CustomUnmanTimeNumber", number.Number) + +CONF_SENSITIVITY = "sensitivity" +CONF_CUSTOM_MODE = "custom_mode" +CONF_EXISTENCE_THRESHOLD = "existence_threshold" +CONF_MOTION_THRESHOLD = "motion_threshold" +CONF_MOTION_TRIGGER = "motion_trigger" +CONF_MOTION_TO_REST = "motion_to_rest" +CONF_CUSTOM_UNMAN_TIME = "custom_unman_time" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_SENSITIVITY): number.number_schema( + SensitivityNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:archive-check-outline", + ), + cv.Optional(CONF_CUSTOM_MODE): number.number_schema( + CustomModeNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:cog", + ), + cv.Optional(CONF_EXISTENCE_THRESHOLD): number.number_schema( + ExistenceThresholdNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + ), + cv.Optional(CONF_MOTION_THRESHOLD): number.number_schema( + MotionThresholdNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + ), + cv.Optional(CONF_MOTION_TRIGGER): number.number_schema( + MotionTriggerTimeNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:camera-timer", + unit_of_measurement="ms", + ), + cv.Optional(CONF_MOTION_TO_REST): number.number_schema( + MotionToRestTimeNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:camera-timer", + unit_of_measurement="ms", + ), + cv.Optional(CONF_CUSTOM_UNMAN_TIME): number.number_schema( + CustomUnmanTimeNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:camera-timer", + unit_of_measurement="s", + ), + } +) + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if sensitivity_config := config.get(CONF_SENSITIVITY): + n = await number.new_number( + sensitivity_config, + min_value=0, + max_value=3, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_sensitivity_number(n)) + if custom_mode_config := config.get(CONF_CUSTOM_MODE): + n = await number.new_number( + custom_mode_config, + min_value=0, + max_value=4, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_custom_mode_number(n)) + if existence_threshold_config := config.get(CONF_EXISTENCE_THRESHOLD): + n = await number.new_number( + existence_threshold_config, + min_value=0, + max_value=250, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_existence_threshold_number(n)) + if motion_threshold_config := config.get(CONF_MOTION_THRESHOLD): + n = await number.new_number( + motion_threshold_config, + min_value=0, + max_value=250, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_motion_threshold_number(n)) + if motion_trigger_config := config.get(CONF_MOTION_TRIGGER): + n = await number.new_number( + motion_trigger_config, + min_value=0, + max_value=150, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_motion_trigger_number(n)) + if motion_to_rest_config := config.get(CONF_MOTION_TO_REST): + n = await number.new_number( + motion_to_rest_config, + min_value=0, + max_value=3000, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_motion_to_rest_number(n)) + if custom_unman_time_config := config.get(CONF_CUSTOM_UNMAN_TIME): + n = await number.new_number( + custom_unman_time_config, + min_value=0, + max_value=3600, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_custom_unman_time_number(n)) diff --git a/esphome/components/seeed_mr24hpc1/number/custom_mode_number.cpp b/esphome/components/seeed_mr24hpc1/number/custom_mode_number.cpp new file mode 100644 index 0000000000..0aebd8fb9f --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/custom_mode_number.cpp @@ -0,0 +1,12 @@ +#include "custom_mode_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void CustomModeNumber::control(float value) { + this->publish_state(value); + this->parent_->set_custom_mode(value); +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/custom_mode_number.h b/esphome/components/seeed_mr24hpc1/number/custom_mode_number.h new file mode 100644 index 0000000000..40ff3f201a --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/custom_mode_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class CustomModeNumber : public number::Number, public Parented { + public: + CustomModeNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/custom_unman_time_number.cpp b/esphome/components/seeed_mr24hpc1/number/custom_unman_time_number.cpp new file mode 100644 index 0000000000..12a8ff69fa --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/custom_unman_time_number.cpp @@ -0,0 +1,9 @@ +#include "custom_unman_time_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void CustomUnmanTimeNumber::control(float value) { this->parent_->set_custom_unman_time(value); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/custom_unman_time_number.h b/esphome/components/seeed_mr24hpc1/number/custom_unman_time_number.h new file mode 100644 index 0000000000..6b871c4c13 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/custom_unman_time_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class CustomUnmanTimeNumber : public number::Number, public Parented { + public: + CustomUnmanTimeNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/existence_threshold_number.cpp b/esphome/components/seeed_mr24hpc1/number/existence_threshold_number.cpp new file mode 100644 index 0000000000..58ef56509e --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/existence_threshold_number.cpp @@ -0,0 +1,9 @@ +#include "existence_threshold_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void ExistenceThresholdNumber::control(float value) { this->parent_->set_existence_threshold(value); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/existence_threshold_number.h b/esphome/components/seeed_mr24hpc1/number/existence_threshold_number.h new file mode 100644 index 0000000000..656bad17de --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/existence_threshold_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class ExistenceThresholdNumber : public number::Number, public Parented { + public: + ExistenceThresholdNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/motion_threshold_number.cpp b/esphome/components/seeed_mr24hpc1/number/motion_threshold_number.cpp new file mode 100644 index 0000000000..d252b4f01d --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/motion_threshold_number.cpp @@ -0,0 +1,9 @@ +#include "motion_threshold_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void MotionThresholdNumber::control(float value) { this->parent_->set_motion_threshold(value); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/motion_threshold_number.h b/esphome/components/seeed_mr24hpc1/number/motion_threshold_number.h new file mode 100644 index 0000000000..e8ae37b96f --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/motion_threshold_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class MotionThresholdNumber : public number::Number, public Parented { + public: + MotionThresholdNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/motion_trigger_time_number.cpp b/esphome/components/seeed_mr24hpc1/number/motion_trigger_time_number.cpp new file mode 100644 index 0000000000..fc7659dc54 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/motion_trigger_time_number.cpp @@ -0,0 +1,9 @@ +#include "motion_trigger_time_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void MotionTriggerTimeNumber::control(float value) { this->parent_->set_motion_trigger_time(value); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/motion_trigger_time_number.h b/esphome/components/seeed_mr24hpc1/number/motion_trigger_time_number.h new file mode 100644 index 0000000000..996356e237 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/motion_trigger_time_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class MotionTriggerTimeNumber : public number::Number, public Parented { + public: + MotionTriggerTimeNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/motiontorest_time_number.cpp b/esphome/components/seeed_mr24hpc1/number/motiontorest_time_number.cpp new file mode 100644 index 0000000000..f598e6686c --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/motiontorest_time_number.cpp @@ -0,0 +1,9 @@ +#include "motiontorest_time_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void MotionToRestTimeNumber::control(float value) { this->parent_->set_motion_to_rest_time(value); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/motiontorest_time_number.h b/esphome/components/seeed_mr24hpc1/number/motiontorest_time_number.h new file mode 100644 index 0000000000..559d23fdeb --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/motiontorest_time_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class MotionToRestTimeNumber : public number::Number, public Parented { + public: + MotionToRestTimeNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/sensitivity_number.cpp b/esphome/components/seeed_mr24hpc1/number/sensitivity_number.cpp new file mode 100644 index 0000000000..d903dfd818 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/sensitivity_number.cpp @@ -0,0 +1,9 @@ +#include "sensitivity_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void SensitivityNumber::control(float value) { this->parent_->set_sensitivity(value); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/sensitivity_number.h b/esphome/components/seeed_mr24hpc1/number/sensitivity_number.h new file mode 100644 index 0000000000..fee33521d0 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/sensitivity_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class SensitivityNumber : public number::Number, public Parented { + public: + SensitivityNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp new file mode 100644 index 0000000000..1cf9bd300a --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp @@ -0,0 +1,890 @@ +#include "seeed_mr24hpc1.h" + +#include "esphome/core/log.h" + +#include + +namespace esphome { +namespace seeed_mr24hpc1 { + +static const char *const TAG = "seeed_mr24hpc1"; + +// Prints the component's configuration data. dump_config() prints all of the component's configuration +// items in an easy-to-read format, including the configuration key-value pairs. +void MR24HPC1Component::dump_config() { + ESP_LOGCONFIG(TAG, "MR24HPC1:"); +#ifdef USE_TEXT_SENSOR + LOG_TEXT_SENSOR(" ", "Heartbeat Text Sensor", this->heartbeat_state_text_sensor_); + LOG_TEXT_SENSOR(" ", "Product Model Text Sensor", this->product_model_text_sensor_); + LOG_TEXT_SENSOR(" ", "Product ID Text Sensor", this->product_id_text_sensor_); + LOG_TEXT_SENSOR(" ", "Hardware Model Text Sensor", this->hardware_model_text_sensor_); + LOG_TEXT_SENSOR(" ", "Firware Verison Text Sensor", this->firware_version_text_sensor_); + LOG_TEXT_SENSOR(" ", "Keep Away Text Sensor", this->keep_away_text_sensor_); + LOG_TEXT_SENSOR(" ", "Motion Status Text Sensor", this->motion_status_text_sensor_); + LOG_TEXT_SENSOR(" ", "Custom Mode End Text Sensor", this->custom_mode_end_text_sensor_); +#endif +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Has Target Binary Sensor", this->has_target_binary_sensor_); +#endif +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Custom Presence Of Detection Sensor", this->custom_presence_of_detection_sensor_); + LOG_SENSOR(" ", "Movement Signs Sensor", this->movement_signs_sensor_); + LOG_SENSOR(" ", "Custom Motion Distance Sensor", this->custom_motion_distance_sensor_); + LOG_SENSOR(" ", "Custom Spatial Static Sensor", this->custom_spatial_static_value_sensor_); + LOG_SENSOR(" ", "Custom Spatial Motion Sensor", this->custom_spatial_motion_value_sensor_); + LOG_SENSOR(" ", "Custom Motion Speed Sensor", this->custom_motion_speed_sensor_); + LOG_SENSOR(" ", "Custom Mode Num Sensor", this->custom_mode_num_sensor_); +#endif +#ifdef USE_SWITCH + LOG_SWITCH(" ", "Underly Open Function Switch", this->underlying_open_function_switch_); +#endif +#ifdef USE_BUTTON + LOG_BUTTON(" ", "Restart Button", this->restart_button_); + LOG_BUTTON(" ", "Custom Set End Button", this->custom_set_end_button_); +#endif +#ifdef USE_SELECT + LOG_SELECT(" ", "Scene Mode Select", this->scene_mode_select_); + LOG_SELECT(" ", "Unman Time Select", this->unman_time_select_); + LOG_SELECT(" ", "Existence Boundary Select", this->existence_boundary_select_); + LOG_SELECT(" ", "Motion Boundary Select", this->motion_boundary_select_); +#endif +#ifdef USE_NUMBER + LOG_NUMBER(" ", "Sensitivity Number", this->sensitivity_number_); + LOG_NUMBER(" ", "Custom Mode Number", this->custom_mode_number_); + LOG_NUMBER(" ", "Existence Threshold Number", this->existence_threshold_number_); + LOG_NUMBER(" ", "Motion Threshold Number", this->motion_threshold_number_); + LOG_NUMBER(" ", "Motion Trigger Time Number", this->motion_trigger_number_); + LOG_NUMBER(" ", "Motion To Rest Time Number", this->motion_to_rest_number_); + LOG_NUMBER(" ", "Custom Unman Time Number", this->custom_unman_time_number_); +#endif +} + +// Initialisation functions +void MR24HPC1Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MR24HPC1..."); + this->check_uart_settings(115200); + + if (this->custom_mode_number_ != nullptr) { + this->custom_mode_number_->publish_state(0); // Zero out the custom mode + } + if (this->custom_mode_num_sensor_ != nullptr) { + this->custom_mode_num_sensor_->publish_state(0); + } + if (this->custom_mode_end_text_sensor_ != nullptr) { + this->custom_mode_end_text_sensor_->publish_state("Not in custom mode"); + } + this->set_custom_end_mode(); + this->poll_time_base_func_check_ = true; + this->check_dev_inf_sign_ = true; + this->sg_start_query_data_ = STANDARD_FUNCTION_QUERY_PRODUCT_MODE; + this->sg_data_len_ = 0; + this->sg_frame_len_ = 0; + this->sg_recv_data_state_ = FRAME_IDLE; + this->s_output_info_switch_flag_ = OUTPUT_SWITCH_INIT; + + memset(this->c_product_mode_, 0, PRODUCT_BUF_MAX_SIZE); + memset(this->c_product_id_, 0, PRODUCT_BUF_MAX_SIZE); + memset(this->c_firmware_version_, 0, PRODUCT_BUF_MAX_SIZE); + memset(this->c_hardware_model_, 0, PRODUCT_BUF_MAX_SIZE); + memset(this->sg_frame_prase_buf_, 0, FRAME_BUF_MAX_SIZE); + memset(this->sg_frame_buf_, 0, FRAME_BUF_MAX_SIZE); + + this->set_interval(8000, [this]() { this->update_(); }); + ESP_LOGCONFIG(TAG, "Set up MR24HPC1 complete"); +} + +// Timed polling of radar data +void MR24HPC1Component::update_() { + this->get_radar_output_information_switch(); // Query the key status every so often + this->poll_time_base_func_check_ = true; // Query the base functionality information at regular intervals +} + +// main loop +void MR24HPC1Component::loop() { + uint8_t byte; + + // Is there data on the serial port + while (this->available()) { + this->read_byte(&byte); + this->r24_split_data_frame_(byte); // split data frame + } + + if ((this->s_output_info_switch_flag_ == OUTPUT_SWTICH_OFF) && + (this->sg_start_query_data_ > CUSTOM_FUNCTION_QUERY_TIME_OF_ENTER_UNMANNED) && (!this->check_dev_inf_sign_)) { + this->sg_start_query_data_ = STANDARD_FUNCTION_QUERY_SCENE_MODE; + } else if ((this->s_output_info_switch_flag_ == OUTPUT_SWITCH_ON) && + (this->sg_start_query_data_ < CUSTOM_FUNCTION_QUERY_EXISTENCE_BOUNDARY) && (!this->check_dev_inf_sign_)) { + this->sg_start_query_data_ = CUSTOM_FUNCTION_QUERY_EXISTENCE_BOUNDARY; + } else if (this->check_dev_inf_sign_ && (this->sg_start_query_data_ > STANDARD_FUNCTION_QUERY_HARDWARE_MODE)) { + // First time power up information polling + this->sg_start_query_data_ = STANDARD_FUNCTION_QUERY_PRODUCT_MODE; + } + + // Polling Functions + if (this->poll_time_base_func_check_) { + switch (this->sg_start_query_data_) { + case STANDARD_FUNCTION_QUERY_PRODUCT_MODE: + this->get_product_mode(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_PRODUCT_ID: + this->get_product_id(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_FIRMWARE_VERSION: + this->get_product_mode(); + this->get_product_id(); + this->get_firmware_version(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_HARDWARE_MODE: // Above is the equipment information + this->get_product_mode(); + this->get_product_id(); + this->get_hardware_model(); + this->sg_start_query_data_++; + this->check_dev_inf_sign_ = false; + break; + case STANDARD_FUNCTION_QUERY_SCENE_MODE: + this->get_scene_mode(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_SENSITIVITY: + this->get_sensitivity(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_UNMANNED_TIME: + this->get_unmanned_time(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_HUMAN_STATUS: + this->get_human_status(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_HUMAN_MOTION_INF: + this->get_human_motion_info(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_BODY_MOVE_PARAMETER: + this->get_body_motion_params(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_KEEPAWAY_STATUS: // The above is the basic functional information + this->get_keep_away(); + this->sg_start_query_data_++; + break; + case STANDARD_QUERY_CUSTOM_MODE: + this->get_custom_mode(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_HEARTBEAT_STATE: + this->get_heartbeat_packet(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_EXISTENCE_BOUNDARY: + this->get_existence_boundary(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_MOTION_BOUNDARY: + this->get_motion_boundary(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_EXISTENCE_THRESHOLD: + this->get_existence_threshold(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_MOTION_THRESHOLD: + this->get_motion_threshold(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_MOTION_TRIGGER_TIME: + this->get_motion_trigger_time(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_MOTION_TO_REST_TIME: + this->get_motion_to_rest_time(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_TIME_OF_ENTER_UNMANNED: + this->get_custom_unman_time(); + this->sg_start_query_data_++; + if (this->s_output_info_switch_flag_ == OUTPUT_SWTICH_OFF) { + this->poll_time_base_func_check_ = false; // Avoiding high-speed polling that can cause the device to jam + } + break; + case UNDERLY_FUNCTION_QUERY_HUMAN_STATUS: + this->get_human_status(); + this->sg_start_query_data_++; + break; + case UNDERLY_FUNCTION_QUERY_SPATIAL_STATIC_VALUE: + this->get_spatial_static_value(); + this->sg_start_query_data_++; + break; + case UNDERLY_FUNCTION_QUERY_SPATIAL_MOTION_VALUE: + this->get_spatial_motion_value(); + this->sg_start_query_data_++; + break; + case UNDERLY_FUNCTION_QUERY_DISTANCE_OF_STATIC_OBJECT: + this->get_distance_of_static_object(); + this->sg_start_query_data_++; + break; + case UNDERLY_FUNCTION_QUERY_DISTANCE_OF_MOVING_OBJECT: + this->get_distance_of_moving_object(); + this->sg_start_query_data_++; + break; + case UNDERLY_FUNCTION_QUERY_TARGET_MOVEMENT_SPEED: + this->get_target_movement_speed(); + this->sg_start_query_data_++; + this->poll_time_base_func_check_ = false; // Avoiding high-speed polling that can cause the device to jam + break; + default: + break; + } + } +} + +// Calculate CRC check digit +static uint8_t get_frame_crc_sum(const uint8_t *data, int len) { + unsigned int crc_sum = 0; + for (int i = 0; i < len - 3; i++) { + crc_sum += data[i]; + } + return crc_sum & 0xff; +} + +// Check that the check digit is correct +static int get_frame_check_status(uint8_t *data, int len) { + uint8_t crc_sum = get_frame_crc_sum(data, len); + uint8_t verified = data[len - 3]; + return (verified == crc_sum) ? 1 : 0; +} + +// split data frame +void MR24HPC1Component::r24_split_data_frame_(uint8_t value) { + switch (this->sg_recv_data_state_) { + case FRAME_IDLE: // starting value + if (FRAME_HEADER1_VALUE == value) { + this->sg_recv_data_state_ = FRAME_HEADER2; + } + break; + case FRAME_HEADER2: + if (FRAME_HEADER2_VALUE == value) { + this->sg_frame_buf_[0] = FRAME_HEADER1_VALUE; + this->sg_frame_buf_[1] = FRAME_HEADER2_VALUE; + this->sg_recv_data_state_ = FRAME_CTL_WORD; + } else { + this->sg_recv_data_state_ = FRAME_IDLE; + ESP_LOGD(TAG, "FRAME_IDLE ERROR value:%x", value); + } + break; + case FRAME_CTL_WORD: + this->sg_frame_buf_[2] = value; + this->sg_recv_data_state_ = FRAME_CMD_WORD; + break; + case FRAME_CMD_WORD: + this->sg_frame_buf_[3] = value; + this->sg_recv_data_state_ = FRAME_DATA_LEN_H; + break; + case FRAME_DATA_LEN_H: + if (value <= 4) { + this->sg_data_len_ = value * 256; + this->sg_frame_buf_[4] = value; + this->sg_recv_data_state_ = FRAME_DATA_LEN_L; + } else { + this->sg_data_len_ = 0; + this->sg_recv_data_state_ = FRAME_IDLE; + ESP_LOGD(TAG, "FRAME_DATA_LEN_H ERROR value:%x", value); + } + break; + case FRAME_DATA_LEN_L: + this->sg_data_len_ += value; + if (this->sg_data_len_ > 32) { + ESP_LOGD(TAG, "len=%d, FRAME_DATA_LEN_L ERROR value:%x", this->sg_data_len_, value); + this->sg_data_len_ = 0; + this->sg_recv_data_state_ = FRAME_IDLE; + } else { + this->sg_frame_buf_[5] = value; + this->sg_frame_len_ = 6; + this->sg_recv_data_state_ = FRAME_DATA_BYTES; + } + break; + case FRAME_DATA_BYTES: + this->sg_data_len_ -= 1; + this->sg_frame_buf_[this->sg_frame_len_++] = value; + if (this->sg_data_len_ <= 0) { + this->sg_recv_data_state_ = FRAME_DATA_CRC; + } + break; + case FRAME_DATA_CRC: + this->sg_frame_buf_[this->sg_frame_len_++] = value; + this->sg_recv_data_state_ = FRAME_TAIL1; + break; + case FRAME_TAIL1: + if (FRAME_TAIL1_VALUE == value) { + this->sg_recv_data_state_ = FRAME_TAIL2; + } else { + this->sg_recv_data_state_ = FRAME_IDLE; + this->sg_frame_len_ = 0; + this->sg_data_len_ = 0; + ESP_LOGD(TAG, "FRAME_TAIL1 ERROR value:%x", value); + } + break; + case FRAME_TAIL2: + if (FRAME_TAIL2_VALUE == value) { + this->sg_frame_buf_[this->sg_frame_len_++] = FRAME_TAIL1_VALUE; + this->sg_frame_buf_[this->sg_frame_len_++] = FRAME_TAIL2_VALUE; + memcpy(this->sg_frame_prase_buf_, this->sg_frame_buf_, this->sg_frame_len_); + if (get_frame_check_status(this->sg_frame_prase_buf_, this->sg_frame_len_)) { + this->r24_parse_data_frame_(this->sg_frame_prase_buf_, this->sg_frame_len_); + } else { + ESP_LOGD(TAG, "frame check failer!"); + } + } else { + ESP_LOGD(TAG, "FRAME_TAIL2 ERROR value:%x", value); + } + memset(this->sg_frame_prase_buf_, 0, FRAME_BUF_MAX_SIZE); + memset(this->sg_frame_buf_, 0, FRAME_BUF_MAX_SIZE); + this->sg_frame_len_ = 0; + this->sg_data_len_ = 0; + this->sg_recv_data_state_ = FRAME_IDLE; + break; + default: + this->sg_recv_data_state_ = FRAME_IDLE; + } +} + +// Parses data frames related to product information +void MR24HPC1Component::r24_frame_parse_product_information_(uint8_t *data) { + uint16_t product_len = encode_uint16(data[FRAME_COMMAND_WORD_INDEX + 1], data[FRAME_COMMAND_WORD_INDEX + 2]); + if (data[FRAME_COMMAND_WORD_INDEX] == COMMAND_PRODUCT_MODE) { + if ((this->product_model_text_sensor_ != nullptr) && (product_len < PRODUCT_BUF_MAX_SIZE)) { + memset(this->c_product_mode_, 0, PRODUCT_BUF_MAX_SIZE); + memcpy(this->c_product_mode_, &data[FRAME_DATA_INDEX], product_len); + this->product_model_text_sensor_->publish_state(this->c_product_mode_); + } else { + ESP_LOGD(TAG, "Reply: get product_mode error!"); + } + } else if (data[FRAME_COMMAND_WORD_INDEX] == COMMAND_PRODUCT_ID) { + if ((this->product_id_text_sensor_ != nullptr) && (product_len < PRODUCT_BUF_MAX_SIZE)) { + memset(this->c_product_id_, 0, PRODUCT_BUF_MAX_SIZE); + memcpy(this->c_product_id_, &data[FRAME_DATA_INDEX], product_len); + this->product_id_text_sensor_->publish_state(this->c_product_id_); + } else { + ESP_LOGD(TAG, "Reply: get productId error!"); + } + } else if (data[FRAME_COMMAND_WORD_INDEX] == COMMAND_HARDWARE_MODEL) { + if ((this->hardware_model_text_sensor_ != nullptr) && (product_len < PRODUCT_BUF_MAX_SIZE)) { + memset(this->c_hardware_model_, 0, PRODUCT_BUF_MAX_SIZE); + memcpy(this->c_hardware_model_, &data[FRAME_DATA_INDEX], product_len); + this->hardware_model_text_sensor_->publish_state(this->c_hardware_model_); + ESP_LOGD(TAG, "Reply: get hardware_model :%s", this->c_hardware_model_); + } else { + ESP_LOGD(TAG, "Reply: get hardwareModel error!"); + } + } else if (data[FRAME_COMMAND_WORD_INDEX] == COMMAND_FIRMWARE_VERSION) { + if ((this->firware_version_text_sensor_ != nullptr) && (product_len < PRODUCT_BUF_MAX_SIZE)) { + memset(this->c_firmware_version_, 0, PRODUCT_BUF_MAX_SIZE); + memcpy(this->c_firmware_version_, &data[FRAME_DATA_INDEX], product_len); + this->firware_version_text_sensor_->publish_state(this->c_firmware_version_); + } else { + ESP_LOGD(TAG, "Reply: get firmwareVersion error!"); + } + } +} + +// Parsing the underlying open parameters +void MR24HPC1Component::r24_frame_parse_open_underlying_information_(uint8_t *data) { + if (data[FRAME_COMMAND_WORD_INDEX] == 0x00) { + if (this->underlying_open_function_switch_ != nullptr) { + this->underlying_open_function_switch_->publish_state( + data[FRAME_DATA_INDEX]); // Underlying Open Parameter Switch Status Updates + } + if (data[FRAME_DATA_INDEX]) { + this->s_output_info_switch_flag_ = OUTPUT_SWITCH_ON; + } else { + this->s_output_info_switch_flag_ = OUTPUT_SWTICH_OFF; + } + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x01) { + if (this->custom_spatial_static_value_sensor_ != nullptr) { + this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } + if (this->custom_presence_of_detection_sensor_ != nullptr) { + this->custom_presence_of_detection_sensor_->publish_state(data[FRAME_DATA_INDEX + 1] * 0.5f); + } + if (this->custom_spatial_motion_value_sensor_ != nullptr) { + this->custom_spatial_motion_value_sensor_->publish_state(data[FRAME_DATA_INDEX + 2]); + } + if (this->custom_motion_distance_sensor_ != nullptr) { + this->custom_motion_distance_sensor_->publish_state(data[FRAME_DATA_INDEX + 3] * 0.5f); + } + if (this->custom_motion_speed_sensor_ != nullptr) { + this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX + 4] - 10) * 0.5f); + } + } else if ((data[FRAME_COMMAND_WORD_INDEX] == 0x06) || (data[FRAME_COMMAND_WORD_INDEX] == 0x86)) { + // none:0x00 close_to:0x01 far_away:0x02 + if ((this->keep_away_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < 3)) { + this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]); + } + } else if ((this->movement_signs_sensor_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x07) || (data[FRAME_COMMAND_WORD_INDEX] == 0x87))) { + this->movement_signs_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } else if ((this->existence_threshold_number_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x08) || (data[FRAME_COMMAND_WORD_INDEX] == 0x88))) { + this->existence_threshold_number_->publish_state(data[FRAME_DATA_INDEX]); + } else if ((this->motion_threshold_number_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x09) || (data[FRAME_COMMAND_WORD_INDEX] == 0x89))) { + this->motion_threshold_number_->publish_state(data[FRAME_DATA_INDEX]); + } else if ((this->existence_boundary_select_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x0a) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8a))) { + if (this->existence_boundary_select_->has_index(data[FRAME_DATA_INDEX] - 1)) { + this->existence_boundary_select_->publish_state(S_BOUNDARY_STR[data[FRAME_DATA_INDEX] - 1]); + } + } else if ((this->motion_boundary_select_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x0b) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8b))) { + if (this->motion_boundary_select_->has_index(data[FRAME_DATA_INDEX] - 1)) { + this->motion_boundary_select_->publish_state(S_BOUNDARY_STR[data[FRAME_DATA_INDEX] - 1]); + } + } else if ((this->motion_trigger_number_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x0c) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8c))) { + uint32_t motion_trigger_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1], + data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]); + this->motion_trigger_number_->publish_state(motion_trigger_time); + } else if ((this->motion_to_rest_number_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x0d) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8d))) { + uint32_t move_to_rest_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1], + data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]); + this->motion_to_rest_number_->publish_state(move_to_rest_time); + } else if ((this->custom_unman_time_number_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x0e) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8e))) { + uint32_t enter_unmanned_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1], + data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]); + float custom_unmanned_time = enter_unmanned_time / 1000.0; + this->custom_unman_time_number_->publish_state(custom_unmanned_time); + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x80) { + if (data[FRAME_DATA_INDEX]) { + this->s_output_info_switch_flag_ = OUTPUT_SWITCH_ON; + } else { + this->s_output_info_switch_flag_ = OUTPUT_SWTICH_OFF; + } + if (this->underlying_open_function_switch_ != nullptr) { + this->underlying_open_function_switch_->publish_state(data[FRAME_DATA_INDEX]); + } + } else if ((this->custom_spatial_static_value_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x81)) { + this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } else if ((this->custom_spatial_motion_value_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x82)) { + this->custom_spatial_motion_value_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } else if ((this->custom_presence_of_detection_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x83)) { + this->custom_presence_of_detection_sensor_->publish_state( + S_PRESENCE_OF_DETECTION_RANGE_STR[data[FRAME_DATA_INDEX]]); + } else if ((this->custom_motion_distance_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x84)) { + this->custom_motion_distance_sensor_->publish_state(data[FRAME_DATA_INDEX] * 0.5f); + } else if ((this->custom_motion_speed_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x85)) { + this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX] - 10) * 0.5f); + } +} + +void MR24HPC1Component::r24_parse_data_frame_(uint8_t *data, uint8_t len) { + switch (data[FRAME_CONTROL_WORD_INDEX]) { + case 0x01: { + if ((this->heartbeat_state_text_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x01)) { + this->heartbeat_state_text_sensor_->publish_state("Equipment Normal"); + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x02) { + ESP_LOGD(TAG, "Reply: query restart packet"); + } else if (this->heartbeat_state_text_sensor_ != nullptr) { + this->heartbeat_state_text_sensor_->publish_state("Equipment Abnormal"); + } + } break; + case 0x02: { + this->r24_frame_parse_product_information_(data); + } break; + case 0x05: { + this->r24_frame_parse_work_status_(data); + } break; + case 0x08: { + this->r24_frame_parse_open_underlying_information_(data); + } break; + case 0x80: { + this->r24_frame_parse_human_information_(data); + } break; + default: + ESP_LOGD(TAG, "control word:0x%02X not found", data[FRAME_CONTROL_WORD_INDEX]); + break; + } +} + +void MR24HPC1Component::r24_frame_parse_work_status_(uint8_t *data) { + if (data[FRAME_COMMAND_WORD_INDEX] == 0x01) { + ESP_LOGD(TAG, "Reply: get radar init status 0x%02X", data[FRAME_DATA_INDEX]); + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x07) { + if ((this->scene_mode_select_ != nullptr) && (this->scene_mode_select_->has_index(data[FRAME_DATA_INDEX]))) { + this->scene_mode_select_->publish_state(S_SCENE_STR[data[FRAME_DATA_INDEX]]); + } else { + ESP_LOGD(TAG, "Select has index offset %d Error", data[FRAME_DATA_INDEX]); + } + } else if ((this->sensitivity_number_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x08) || (data[FRAME_COMMAND_WORD_INDEX] == 0x88))) { + // 1-3 + this->sensitivity_number_->publish_state(data[FRAME_DATA_INDEX]); + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x09) { + // 1-4 + if (this->custom_mode_num_sensor_ != nullptr) { + this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } + if (this->custom_mode_number_ != nullptr) { + this->custom_mode_number_->publish_state(0); + } + if (this->custom_mode_end_text_sensor_ != nullptr) { + this->custom_mode_end_text_sensor_->publish_state("Setup in progress..."); + } + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x81) { + ESP_LOGD(TAG, "Reply: get radar init status 0x%02X", data[FRAME_DATA_INDEX]); + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x87) { + if ((this->scene_mode_select_ != nullptr) && (this->scene_mode_select_->has_index(data[FRAME_DATA_INDEX]))) { + this->scene_mode_select_->publish_state(S_SCENE_STR[data[FRAME_DATA_INDEX]]); + } else { + ESP_LOGD(TAG, "Select has index offset %d Error", data[FRAME_DATA_INDEX]); + } + } else if ((this->custom_mode_end_text_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x0A)) { + this->custom_mode_end_text_sensor_->publish_state("Set Success!"); + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x89) { + if (data[FRAME_DATA_INDEX] == 0) { + if (this->custom_mode_end_text_sensor_ != nullptr) { + this->custom_mode_end_text_sensor_->publish_state("Not in custom mode"); + } + if (this->custom_mode_number_ != nullptr) { + this->custom_mode_number_->publish_state(0); + } + if (this->custom_mode_num_sensor_ != nullptr) { + this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } + } else { + if (this->custom_mode_num_sensor_ != nullptr) { + this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } + } + } else { + ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, data[FRAME_COMMAND_WORD_INDEX]); + } +} + +void MR24HPC1Component::r24_frame_parse_human_information_(uint8_t *data) { + if ((this->has_target_binary_sensor_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x01) || (data[FRAME_COMMAND_WORD_INDEX] == 0x81))) { + this->has_target_binary_sensor_->publish_state(S_SOMEONE_EXISTS_STR[data[FRAME_DATA_INDEX]]); + } else if ((this->motion_status_text_sensor_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x02) || (data[FRAME_COMMAND_WORD_INDEX] == 0x82))) { + if (data[FRAME_DATA_INDEX] < 3) { + this->motion_status_text_sensor_->publish_state(S_MOTION_STATUS_STR[data[FRAME_DATA_INDEX]]); + } + } else if ((this->movement_signs_sensor_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x03) || (data[FRAME_COMMAND_WORD_INDEX] == 0x83))) { + this->movement_signs_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } else if ((this->unman_time_select_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x0A) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8A))) { + // none:0x00 1s:0x01 30s:0x02 1min:0x03 2min:0x04 5min:0x05 10min:0x06 30min:0x07 1hour:0x08 + if (data[FRAME_DATA_INDEX] < 9) { + this->unman_time_select_->publish_state(S_UNMANNED_TIME_STR[data[FRAME_DATA_INDEX]]); + } + } else if ((this->keep_away_text_sensor_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x0B) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8B))) { + // none:0x00 close_to:0x01 far_away:0x02 + if (data[FRAME_DATA_INDEX] < 3) { + this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]); + } + } else { + ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, data[FRAME_COMMAND_WORD_INDEX]); + } +} + +// Sending data frames +void MR24HPC1Component::send_query_(const uint8_t *query, size_t string_length) { + this->write_array(query, string_length); +} + +// Send Heartbeat Packet Command +void MR24HPC1Component::get_heartbeat_packet() { this->send_query_(GET_HEARTBEAT, sizeof(GET_HEARTBEAT)); } + +// Issuance of the underlying open parameter query command +void MR24HPC1Component::get_radar_output_information_switch() { + this->send_query_(GET_RADAR_OUTPUT_INFORMATION_SWITCH, sizeof(GET_RADAR_OUTPUT_INFORMATION_SWITCH)); +} + +// Issuance of product model orders +void MR24HPC1Component::get_product_mode() { this->send_query_(GET_PRODUCT_MODE, sizeof(GET_PRODUCT_MODE)); } + +// Issuing the Get Product ID command +void MR24HPC1Component::get_product_id() { this->send_query_(GET_PRODUCT_ID, sizeof(GET_PRODUCT_ID)); } + +// Issuing hardware model commands +void MR24HPC1Component::get_hardware_model() { this->send_query_(GET_HARDWARE_MODEL, sizeof(GET_HARDWARE_MODEL)); } + +// Issuing software version commands +void MR24HPC1Component::get_firmware_version() { + this->send_query_(GET_FIRMWARE_VERSION, sizeof(GET_FIRMWARE_VERSION)); +} + +void MR24HPC1Component::get_human_status() { this->send_query_(GET_HUMAN_STATUS, sizeof(GET_HUMAN_STATUS)); } + +void MR24HPC1Component::get_human_motion_info() { + this->send_query_(GET_HUMAN_MOTION_INFORMATION, sizeof(GET_HUMAN_MOTION_INFORMATION)); +} + +void MR24HPC1Component::get_body_motion_params() { + this->send_query_(GET_BODY_MOTION_PARAMETERS, sizeof(GET_BODY_MOTION_PARAMETERS)); +} + +void MR24HPC1Component::get_keep_away() { this->send_query_(GET_KEEP_AWAY, sizeof(GET_KEEP_AWAY)); } + +void MR24HPC1Component::get_scene_mode() { this->send_query_(GET_SCENE_MODE, sizeof(GET_SCENE_MODE)); } + +void MR24HPC1Component::get_sensitivity() { this->send_query_(GET_SENSITIVITY, sizeof(GET_SENSITIVITY)); } + +void MR24HPC1Component::get_unmanned_time() { this->send_query_(GET_UNMANNED_TIME, sizeof(GET_UNMANNED_TIME)); } + +void MR24HPC1Component::get_custom_mode() { this->send_query_(GET_CUSTOM_MODE, sizeof(GET_CUSTOM_MODE)); } + +void MR24HPC1Component::get_existence_boundary() { + this->send_query_(GET_EXISTENCE_BOUNDARY, sizeof(GET_EXISTENCE_BOUNDARY)); +} + +void MR24HPC1Component::get_motion_boundary() { this->send_query_(GET_MOTION_BOUNDARY, sizeof(GET_MOTION_BOUNDARY)); } + +void MR24HPC1Component::get_spatial_static_value() { + this->send_query_(GET_SPATIAL_STATIC_VALUE, sizeof(GET_SPATIAL_STATIC_VALUE)); +} + +void MR24HPC1Component::get_spatial_motion_value() { + this->send_query_(GET_SPATIAL_MOTION_VALUE, sizeof(GET_SPATIAL_MOTION_VALUE)); +} + +void MR24HPC1Component::get_distance_of_static_object() { + this->send_query_(GET_DISTANCE_OF_STATIC_OBJECT, sizeof(GET_DISTANCE_OF_STATIC_OBJECT)); +} + +void MR24HPC1Component::get_distance_of_moving_object() { + this->send_query_(GET_DISTANCE_OF_MOVING_OBJECT, sizeof(GET_DISTANCE_OF_MOVING_OBJECT)); +} + +void MR24HPC1Component::get_target_movement_speed() { + this->send_query_(GET_TARGET_MOVEMENT_SPEED, sizeof(GET_TARGET_MOVEMENT_SPEED)); +} + +void MR24HPC1Component::get_existence_threshold() { + this->send_query_(GET_EXISTENCE_THRESHOLD, sizeof(GET_EXISTENCE_THRESHOLD)); +} + +void MR24HPC1Component::get_motion_threshold() { + this->send_query_(GET_MOTION_THRESHOLD, sizeof(GET_MOTION_THRESHOLD)); +} + +void MR24HPC1Component::get_motion_trigger_time() { + this->send_query_(GET_MOTION_TRIGGER_TIME, sizeof(GET_MOTION_TRIGGER_TIME)); +} + +void MR24HPC1Component::get_motion_to_rest_time() { + this->send_query_(GET_MOTION_TO_REST_TIME, sizeof(GET_MOTION_TO_REST_TIME)); +} + +void MR24HPC1Component::get_custom_unman_time() { + this->send_query_(GET_CUSTOM_UNMAN_TIME, sizeof(GET_CUSTOM_UNMAN_TIME)); +} + +// Logic of setting: After setting, query whether the setting is successful or not! + +void MR24HPC1Component::set_underlying_open_function(bool enable) { + if (enable) { + this->send_query_(UNDERLYING_SWITCH_ON, sizeof(UNDERLYING_SWITCH_ON)); + } else { + this->send_query_(UNDERLYING_SWITCH_OFF, sizeof(UNDERLYING_SWITCH_OFF)); + } + if (this->keep_away_text_sensor_ != nullptr) { + this->keep_away_text_sensor_->publish_state(""); + } + if (this->motion_status_text_sensor_ != nullptr) { + this->motion_status_text_sensor_->publish_state(""); + } + if (this->custom_spatial_static_value_sensor_ != nullptr) { + this->custom_spatial_static_value_sensor_->publish_state(NAN); + } + if (this->custom_spatial_motion_value_sensor_ != nullptr) { + this->custom_spatial_motion_value_sensor_->publish_state(NAN); + } + if (this->custom_motion_distance_sensor_ != nullptr) { + this->custom_motion_distance_sensor_->publish_state(NAN); + } + if (this->custom_presence_of_detection_sensor_ != nullptr) { + this->custom_presence_of_detection_sensor_->publish_state(NAN); + } + if (this->custom_motion_speed_sensor_ != nullptr) { + this->custom_motion_speed_sensor_->publish_state(NAN); + } +} + +void MR24HPC1Component::set_scene_mode(uint8_t value) { + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x07, 0x00, 0x01, value, 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + if (this->custom_mode_number_ != nullptr) { + this->custom_mode_number_->publish_state(0); + } + if (this->custom_mode_num_sensor_ != nullptr) { + this->custom_mode_num_sensor_->publish_state(0); + } + this->get_scene_mode(); + this->get_sensitivity(); + this->get_custom_mode(); + this->get_existence_boundary(); + this->get_motion_boundary(); + this->get_existence_threshold(); + this->get_motion_threshold(); + this->get_motion_trigger_time(); + this->get_motion_to_rest_time(); + this->get_custom_unman_time(); +} + +void MR24HPC1Component::set_sensitivity(uint8_t value) { + if (value == 0x00) + return; + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x08, 0x00, 0x01, value, 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_scene_mode(); + this->get_sensitivity(); +} + +void MR24HPC1Component::set_restart() { + this->send_query_(SET_RESTART, sizeof(SET_RESTART)); + this->check_dev_inf_sign_ = true; +} + +void MR24HPC1Component::set_unman_time(uint8_t value) { + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x80, 0x0a, 0x00, 0x01, value, 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_unmanned_time(); +} + +void MR24HPC1Component::set_custom_mode(uint8_t mode) { + if (mode == 0) { + this->set_custom_end_mode(); // Equivalent to end setting + if (this->custom_mode_number_ != nullptr) { + this->custom_mode_number_->publish_state(0); + } + return; + } + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x09, 0x00, 0x01, mode, 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_existence_boundary(); + this->get_motion_boundary(); + this->get_existence_threshold(); + this->get_motion_threshold(); + this->get_motion_trigger_time(); + this->get_motion_to_rest_time(); + this->get_custom_unman_time(); + this->get_custom_mode(); + this->get_scene_mode(); + this->get_sensitivity(); +} + +void MR24HPC1Component::set_custom_end_mode() { + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x0a, 0x00, 0x01, 0x0F, 0xCB, 0x54, 0x43}; + this->send_query_(send_data, send_data_len); + if (this->custom_mode_number_ != nullptr) { + this->custom_mode_number_->publish_state(0); // Clear setpoints + } + this->get_existence_boundary(); + this->get_motion_boundary(); + this->get_existence_threshold(); + this->get_motion_threshold(); + this->get_motion_trigger_time(); + this->get_motion_to_rest_time(); + this->get_custom_unman_time(); + this->get_custom_mode(); + this->get_scene_mode(); + this->get_sensitivity(); +} + +void MR24HPC1Component::set_existence_boundary(uint8_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x0A, 0x00, 0x01, (uint8_t) (value + 1), 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_existence_boundary(); +} + +void MR24HPC1Component::set_motion_boundary(uint8_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x0B, 0x00, 0x01, (uint8_t) (value + 1), 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_motion_boundary(); +} + +void MR24HPC1Component::set_existence_threshold(uint8_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x08, 0x00, 0x01, value, 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_existence_threshold(); +} + +void MR24HPC1Component::set_motion_threshold(uint8_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x09, 0x00, 0x01, value, 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_motion_threshold(); +} + +void MR24HPC1Component::set_motion_trigger_time(uint8_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint8_t send_data_len = 13; + uint8_t send_data[13] = {0x53, 0x59, 0x08, 0x0C, 0x00, 0x04, 0x00, 0x00, 0x00, value, 0x00, 0x54, 0x43}; + send_data[10] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_motion_trigger_time(); +} + +void MR24HPC1Component::set_motion_to_rest_time(uint16_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint8_t h8_num = (value >> 8) & 0xff; + uint8_t l8_num = value & 0xff; + uint8_t send_data_len = 13; + uint8_t send_data[13] = {0x53, 0x59, 0x08, 0x0D, 0x00, 0x04, 0x00, 0x00, h8_num, l8_num, 0x00, 0x54, 0x43}; + send_data[10] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_motion_to_rest_time(); +} + +void MR24HPC1Component::set_custom_unman_time(uint16_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint32_t value_ms = value * 1000; + uint8_t h24_num = (value_ms >> 24) & 0xff; + uint8_t h16_num = (value_ms >> 16) & 0xff; + uint8_t h8_num = (value_ms >> 8) & 0xff; + uint8_t l8_num = value_ms & 0xff; + uint8_t send_data_len = 13; + uint8_t send_data[13] = {0x53, 0x59, 0x08, 0x0E, 0x00, 0x04, h24_num, h16_num, h8_num, l8_num, 0x00, 0x54, 0x43}; + send_data[10] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_custom_unman_time(); +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.h b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.h new file mode 100644 index 0000000000..8fc61ad37c --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.h @@ -0,0 +1,217 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_NUMBER +#include "esphome/components/number/number.h" +#endif +#ifdef USE_SWITCH +#include "esphome/components/switch/switch.h" +#endif +#ifdef USE_BUTTON +#include "esphome/components/button/button.h" +#endif +#ifdef USE_SELECT +#include "esphome/components/select/select.h" +#endif +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif +#include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" +#include "esphome/core/helpers.h" + +#include "seeed_mr24hpc1_constants.h" + +#include + +namespace esphome { +namespace seeed_mr24hpc1 { + +enum FrameState { + FRAME_IDLE, + FRAME_HEADER2, + FRAME_CTL_WORD, + FRAME_CMD_WORD, + FRAME_DATA_LEN_H, + FRAME_DATA_LEN_L, + FRAME_DATA_BYTES, + FRAME_DATA_CRC, + FRAME_TAIL1, + FRAME_TAIL2, +}; + +enum PollingState { + STANDARD_FUNCTION_QUERY_PRODUCT_MODE = 0, + STANDARD_FUNCTION_QUERY_PRODUCT_ID, + STANDARD_FUNCTION_QUERY_FIRMWARE_VERSION, + STANDARD_FUNCTION_QUERY_HARDWARE_MODE, // Above is the equipment information + STANDARD_FUNCTION_QUERY_SCENE_MODE, + STANDARD_FUNCTION_QUERY_SENSITIVITY, + STANDARD_FUNCTION_QUERY_UNMANNED_TIME, + STANDARD_FUNCTION_QUERY_HUMAN_STATUS, + STANDARD_FUNCTION_QUERY_HUMAN_MOTION_INF, + STANDARD_FUNCTION_QUERY_BODY_MOVE_PARAMETER, + STANDARD_FUNCTION_QUERY_KEEPAWAY_STATUS, + STANDARD_QUERY_CUSTOM_MODE, + STANDARD_FUNCTION_QUERY_HEARTBEAT_STATE, // Above is the basic function + + CUSTOM_FUNCTION_QUERY_EXISTENCE_BOUNDARY, + CUSTOM_FUNCTION_QUERY_MOTION_BOUNDARY, + CUSTOM_FUNCTION_QUERY_EXISTENCE_THRESHOLD, + CUSTOM_FUNCTION_QUERY_MOTION_THRESHOLD, + CUSTOM_FUNCTION_QUERY_MOTION_TRIGGER_TIME, + CUSTOM_FUNCTION_QUERY_MOTION_TO_REST_TIME, + CUSTOM_FUNCTION_QUERY_TIME_OF_ENTER_UNMANNED, + + UNDERLY_FUNCTION_QUERY_HUMAN_STATUS, + UNDERLY_FUNCTION_QUERY_SPATIAL_STATIC_VALUE, + UNDERLY_FUNCTION_QUERY_SPATIAL_MOTION_VALUE, + UNDERLY_FUNCTION_QUERY_DISTANCE_OF_STATIC_OBJECT, + UNDERLY_FUNCTION_QUERY_DISTANCE_OF_MOVING_OBJECT, + UNDERLY_FUNCTION_QUERY_TARGET_MOVEMENT_SPEED, +}; + +enum OutputSwitch { + OUTPUT_SWITCH_INIT, + OUTPUT_SWITCH_ON, + OUTPUT_SWTICH_OFF, +}; + +static const char *const S_SCENE_STR[5] = {"None", "Living Room", "Bedroom", "Washroom", "Area Detection"}; +static const bool S_SOMEONE_EXISTS_STR[2] = {false, true}; +static const char *const S_MOTION_STATUS_STR[3] = {"None", "Motionless", "Active"}; +static const char *const S_KEEP_AWAY_STR[3] = {"None", "Close", "Away"}; +static const char *const S_UNMANNED_TIME_STR[9] = {"None", "10s", "30s", "1min", "2min", + "5min", "10min", "30min", "60min"}; +static const char *const S_BOUNDARY_STR[10] = {"0.5m", "1.0m", "1.5m", "2.0m", "2.5m", + "3.0m", "3.5m", "4.0m", "4.5m", "5.0m"}; // uint: m +static const float S_PRESENCE_OF_DETECTION_RANGE_STR[7] = {0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f}; // uint: m + +class MR24HPC1Component : public Component, + public uart::UARTDevice { // The class name must be the name defined by text_sensor.py +#ifdef USE_TEXT_SENSOR + SUB_TEXT_SENSOR(heartbeat_state) + SUB_TEXT_SENSOR(product_model) + SUB_TEXT_SENSOR(product_id) + SUB_TEXT_SENSOR(hardware_model) + SUB_TEXT_SENSOR(firware_version) + SUB_TEXT_SENSOR(keep_away) + SUB_TEXT_SENSOR(motion_status) + SUB_TEXT_SENSOR(custom_mode_end) +#endif +#ifdef USE_BINARY_SENSOR + SUB_BINARY_SENSOR(has_target) +#endif +#ifdef USE_SENSOR + SUB_SENSOR(custom_presence_of_detection) + SUB_SENSOR(movement_signs) + SUB_SENSOR(custom_motion_distance) + SUB_SENSOR(custom_spatial_static_value) + SUB_SENSOR(custom_spatial_motion_value) + SUB_SENSOR(custom_motion_speed) + SUB_SENSOR(custom_mode_num) +#endif +#ifdef USE_SWITCH + SUB_SWITCH(underlying_open_function) +#endif +#ifdef USE_BUTTON + SUB_BUTTON(restart) + SUB_BUTTON(custom_set_end) +#endif +#ifdef USE_SELECT + SUB_SELECT(scene_mode) + SUB_SELECT(unman_time) + SUB_SELECT(existence_boundary) + SUB_SELECT(motion_boundary) +#endif +#ifdef USE_NUMBER + SUB_NUMBER(sensitivity) + SUB_NUMBER(custom_mode) + SUB_NUMBER(existence_threshold) + SUB_NUMBER(motion_threshold) + SUB_NUMBER(motion_trigger) + SUB_NUMBER(motion_to_rest) + SUB_NUMBER(custom_unman_time) +#endif + + protected: + char c_product_mode_[PRODUCT_BUF_MAX_SIZE + 1]; + char c_product_id_[PRODUCT_BUF_MAX_SIZE + 1]; + char c_hardware_model_[PRODUCT_BUF_MAX_SIZE + 1]; + char c_firmware_version_[PRODUCT_BUF_MAX_SIZE + 1]; + uint8_t s_output_info_switch_flag_; + uint8_t sg_recv_data_state_; + uint8_t sg_frame_len_; + uint8_t sg_data_len_; + uint8_t sg_frame_buf_[FRAME_BUF_MAX_SIZE]; + uint8_t sg_frame_prase_buf_[FRAME_BUF_MAX_SIZE]; + int sg_start_query_data_; + bool check_dev_inf_sign_; + bool poll_time_base_func_check_; + + void update_(); + void r24_split_data_frame_(uint8_t value); + void r24_parse_data_frame_(uint8_t *data, uint8_t len); + void r24_frame_parse_open_underlying_information_(uint8_t *data); + void r24_frame_parse_work_status_(uint8_t *data); + void r24_frame_parse_product_information_(uint8_t *data); + void r24_frame_parse_human_information_(uint8_t *data); + void send_query_(const uint8_t *query, size_t string_length); + + public: + float get_setup_priority() const override { return esphome::setup_priority::LATE; } + void setup() override; + void dump_config() override; + void loop() override; + + void get_heartbeat_packet(); + void get_radar_output_information_switch(); + void get_product_mode(); + void get_product_id(); + void get_hardware_model(); + void get_firmware_version(); + void get_human_status(); + void get_human_motion_info(); + void get_body_motion_params(); + void get_keep_away(); + void get_scene_mode(); + void get_sensitivity(); + void get_unmanned_time(); + void get_custom_mode(); + void get_existence_boundary(); + void get_motion_boundary(); + void get_spatial_static_value(); + void get_spatial_motion_value(); + void get_distance_of_static_object(); + void get_distance_of_moving_object(); + void get_target_movement_speed(); + void get_existence_threshold(); + void get_motion_threshold(); + void get_motion_trigger_time(); + void get_motion_to_rest_time(); + void get_custom_unman_time(); + + void set_scene_mode(uint8_t value); + void set_underlying_open_function(bool enable); + void set_sensitivity(uint8_t value); + void set_restart(); + void set_unman_time(uint8_t value); + void set_custom_mode(uint8_t mode); + void set_custom_end_mode(); + void set_existence_boundary(uint8_t value); + void set_motion_boundary(uint8_t value); + void set_existence_threshold(uint8_t value); + void set_motion_threshold(uint8_t value); + void set_motion_trigger_time(uint8_t value); + void set_motion_to_rest_time(uint16_t value); + void set_custom_unman_time(uint16_t value); +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1_constants.h b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1_constants.h new file mode 100644 index 0000000000..dafc6c0368 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1_constants.h @@ -0,0 +1,173 @@ +#pragma once + +#include + +namespace esphome { +namespace seeed_mr24hpc1 { + +static const uint8_t FRAME_BUF_MAX_SIZE = 128; +static const uint8_t PRODUCT_BUF_MAX_SIZE = 32; + +static const uint8_t FRAME_CONTROL_WORD_INDEX = 2; +static const uint8_t FRAME_COMMAND_WORD_INDEX = 3; +static const uint8_t FRAME_DATA_INDEX = 6; + +static const uint8_t FRAME_HEADER1_VALUE = 0x53; +static const uint8_t FRAME_HEADER2_VALUE = 0x59; +static const uint8_t FRAME_TAIL1_VALUE = 0x54; +static const uint8_t FRAME_TAIL2_VALUE = 0x43; + +static const uint8_t CONTROL_MAIN = 0x01; +static const uint8_t CONTROL_PRODUCT_INFORMATION = 0x02; +static const uint8_t CONTROL_WORK = 0x05; +static const uint8_t CONTROL_UNDERLYING_FUNCTION = 0x08; +static const uint8_t CONTROL_HUMAN_INFORMATION = 0x80; + +static const uint8_t COMMAND_HEARTBEAT = 0x01; +static const uint8_t COMMAND_RESTART = 0x02; + +static const uint8_t COMMAND_PRODUCT_MODE = 0xA1; +static const uint8_t COMMAND_PRODUCT_ID = 0xA2; +static const uint8_t COMMAND_HARDWARE_MODEL = 0xA3; +static const uint8_t COMMAND_FIRMWARE_VERSION = 0xA4; + +static const uint8_t GET_HEARTBEAT[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_MAIN, COMMAND_HEARTBEAT, 0x00, 0x01, 0x0F, 0xBE, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t SET_RESTART[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_MAIN, COMMAND_RESTART, 0x00, 0x01, 0x0F, 0xBF, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; + +static const uint8_t GET_PRODUCT_MODE[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_PRODUCT_INFORMATION, COMMAND_PRODUCT_MODE, 0x00, 0x01, 0x0F, 0x5F, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_PRODUCT_ID[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_PRODUCT_INFORMATION, COMMAND_PRODUCT_ID, 0x00, 0x01, 0x0F, 0x60, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_HARDWARE_MODEL[] = { + FRAME_HEADER1_VALUE, + FRAME_HEADER2_VALUE, + CONTROL_PRODUCT_INFORMATION, + COMMAND_HARDWARE_MODEL, + 0x00, + 0x01, + 0x0F, + 0x61, + FRAME_TAIL1_VALUE, + FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_FIRMWARE_VERSION[] = { + FRAME_HEADER1_VALUE, + FRAME_HEADER2_VALUE, + CONTROL_PRODUCT_INFORMATION, + COMMAND_FIRMWARE_VERSION, + 0x00, + 0x01, + 0x0F, + 0x62, + FRAME_TAIL1_VALUE, + FRAME_TAIL2_VALUE, +}; + +static const uint8_t GET_SCENE_MODE[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_WORK, 0x87, 0x00, 0x01, 0x0F, 0x48, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_SENSITIVITY[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_WORK, 0x88, 0x00, 0x01, 0x0F, 0x49, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_CUSTOM_MODE[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_WORK, 0x89, 0x00, 0x01, 0x0F, 0x4A, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; + +static const uint8_t UNDERLYING_SWITCH_ON[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x00, 0x00, 0x01, 0x01, 0xB6, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t UNDERLYING_SWITCH_OFF[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x00, 0x00, 0x01, 0x00, 0xB5, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; + +static const uint8_t GET_RADAR_OUTPUT_INFORMATION_SWITCH[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x80, 0x00, 0x01, 0x0F, 0x44, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_SPATIAL_STATIC_VALUE[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x81, 0x00, 0x01, 0x0F, 0x45, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_SPATIAL_MOTION_VALUE[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x82, 0x00, 0x01, 0x0F, 0x46, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_DISTANCE_OF_STATIC_OBJECT[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x83, 0x00, 0x01, 0x0F, 0x47, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_DISTANCE_OF_MOVING_OBJECT[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x84, 0x00, 0x01, 0x0F, 0x48, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_TARGET_MOVEMENT_SPEED[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x85, 0x00, 0x01, 0x0F, 0x49, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_EXISTENCE_THRESHOLD[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x88, 0x00, 0x01, 0x0F, 0x4C, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_MOTION_THRESHOLD[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x89, 0x00, 0x01, 0x0F, 0x4D, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_EXISTENCE_BOUNDARY[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x8A, 0x00, 0x01, 0x0F, 0x4E, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_MOTION_BOUNDARY[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x8B, 0x00, 0x01, 0x0F, 0x4F, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_MOTION_TRIGGER_TIME[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x8C, 0x00, 0x01, 0x0F, 0x50, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_MOTION_TO_REST_TIME[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x8D, 0x00, 0x01, 0x0F, 0x51, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_CUSTOM_UNMAN_TIME[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x8E, 0x00, 0x01, 0x0F, 0x52, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; + +static const uint8_t GET_HUMAN_STATUS[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_HUMAN_INFORMATION, 0x81, 0x00, 0x01, 0x0F, 0xBD, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_HUMAN_MOTION_INFORMATION[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_HUMAN_INFORMATION, 0x82, 0x00, 0x01, 0x0F, 0xBE, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_BODY_MOTION_PARAMETERS[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_HUMAN_INFORMATION, 0x83, 0x00, 0x01, 0x0F, 0xBF, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_UNMANNED_TIME[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_HUMAN_INFORMATION, 0x8A, 0x00, 0x01, 0x0F, 0xC6, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_KEEP_AWAY[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_HUMAN_INFORMATION, 0x8B, 0x00, 0x01, 0x0F, 0xC7, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/__init__.py b/esphome/components/seeed_mr24hpc1/select/__init__.py new file mode 100644 index 0000000000..7da83627b9 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/__init__.py @@ -0,0 +1,103 @@ +import esphome.codegen as cg +from esphome.components import select +import esphome.config_validation as cv +from esphome.const import ( + ENTITY_CATEGORY_CONFIG, +) +from .. import CONF_MR24HPC1_ID, MR24HPC1Component, mr24hpc1_ns + +SceneModeSelect = mr24hpc1_ns.class_("SceneModeSelect", select.Select) +UnmanTimeSelect = mr24hpc1_ns.class_("UnmanTimeSelect", select.Select) +ExistenceBoundarySelect = mr24hpc1_ns.class_("ExistenceBoundarySelect", select.Select) +MotionBoundarySelect = mr24hpc1_ns.class_("MotionBoundarySelect", select.Select) + +CONF_SCENE_MODE = "scene_mode" +CONF_UNMAN_TIME = "unman_time" +CONF_EXISTENCE_BOUNDARY = "existence_boundary" +CONF_MOTION_BOUNDARY = "motion_boundary" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_SCENE_MODE): select.select_schema( + SceneModeSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:hoop-house", + ), + cv.Optional(CONF_UNMAN_TIME): select.select_schema( + UnmanTimeSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:timeline-clock", + ), + cv.Optional(CONF_EXISTENCE_BOUNDARY): select.select_schema( + ExistenceBoundarySelect, + entity_category=ENTITY_CATEGORY_CONFIG, + ), + cv.Optional(CONF_MOTION_BOUNDARY): select.select_schema( + MotionBoundarySelect, + entity_category=ENTITY_CATEGORY_CONFIG, + ), +} + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if scenemode_config := config.get(CONF_SCENE_MODE): + s = await select.new_select( + scenemode_config, + options=["None", "Living Room", "Bedroom", "Washroom", "Area Detection"], + ) + await cg.register_parented(s, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_scene_mode_select(s)) + if unmantime_config := config.get(CONF_UNMAN_TIME): + s = await select.new_select( + unmantime_config, + options=[ + "None", + "10s", + "30s", + "1min", + "2min", + "5min", + "10min", + "30min", + "60min", + ], + ) + await cg.register_parented(s, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_unman_time_select(s)) + if existence_boundary_config := config.get(CONF_EXISTENCE_BOUNDARY): + s = await select.new_select( + existence_boundary_config, + options=[ + "0.5m", + "1.0m", + "1.5m", + "2.0m", + "2.5m", + "3.0m", + "3.5m", + "4.0m", + "4.5m", + "5.0m", + ], + ) + await cg.register_parented(s, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_existence_boundary_select(s)) + if motion_boundary_config := config.get(CONF_MOTION_BOUNDARY): + s = await select.new_select( + motion_boundary_config, + options=[ + "0.5m", + "1.0m", + "1.5m", + "2.0m", + "2.5m", + "3.0m", + "3.5m", + "4.0m", + "4.5m", + "5.0m", + ], + ) + await cg.register_parented(s, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_motion_boundary_select(s)) diff --git a/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.cpp b/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.cpp new file mode 100644 index 0000000000..03c2ec4745 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.cpp @@ -0,0 +1,15 @@ +#include "existence_boundary_select.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void ExistenceBoundarySelect::control(const std::string &value) { + this->publish_state(value); + auto index = this->index_of(value); + if (index.has_value()) { + this->parent_->set_existence_boundary(index.value()); + } +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.h b/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.h new file mode 100644 index 0000000000..ad770a7296 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class ExistenceBoundarySelect : public select::Select, public Parented { + public: + ExistenceBoundarySelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.cpp b/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.cpp new file mode 100644 index 0000000000..619a4f0935 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.cpp @@ -0,0 +1,15 @@ +#include "motion_boundary_select.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void MotionBoundarySelect::control(const std::string &value) { + this->publish_state(value); + auto index = this->index_of(value); + if (index.has_value()) { + this->parent_->set_motion_boundary(index.value()); + } +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.h b/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.h new file mode 100644 index 0000000000..9058e3130b --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class MotionBoundarySelect : public select::Select, public Parented { + public: + MotionBoundarySelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/scene_mode_select.cpp b/esphome/components/seeed_mr24hpc1/select/scene_mode_select.cpp new file mode 100644 index 0000000000..153ae603cf --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/scene_mode_select.cpp @@ -0,0 +1,15 @@ +#include "scene_mode_select.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void SceneModeSelect::control(const std::string &value) { + this->publish_state(value); + auto index = this->index_of(value); + if (index.has_value()) { + this->parent_->set_scene_mode(index.value()); + } +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/scene_mode_select.h b/esphome/components/seeed_mr24hpc1/select/scene_mode_select.h new file mode 100644 index 0000000000..95508d49b0 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/scene_mode_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class SceneModeSelect : public select::Select, public Parented { + public: + SceneModeSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/unman_time_select.cpp b/esphome/components/seeed_mr24hpc1/select/unman_time_select.cpp new file mode 100644 index 0000000000..a9d96c8f67 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/unman_time_select.cpp @@ -0,0 +1,15 @@ +#include "unman_time_select.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void UnmanTimeSelect::control(const std::string &value) { + this->publish_state(value); + auto index = this->index_of(value); + if (index.has_value()) { + this->parent_->set_unman_time(index.value()); + } +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/unman_time_select.h b/esphome/components/seeed_mr24hpc1/select/unman_time_select.h new file mode 100644 index 0000000000..7131988cda --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/unman_time_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class UnmanTimeSelect : public select::Select, public Parented { + public: + UnmanTimeSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/sensor.py b/esphome/components/seeed_mr24hpc1/sensor.py new file mode 100644 index 0000000000..d5eb09e265 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/sensor.py @@ -0,0 +1,82 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_DISTANCE, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_SPEED, + UNIT_METER, +) +from . import CONF_MR24HPC1_ID, MR24HPC1Component + +CONF_CUSTOM_PRESENCE_OF_DETECTION = "custom_presence_of_detection" +CONF_MOVEMENT_SIGNS = "movement_signs" +CONF_CUSTOM_MOTION_DISTANCE = "custom_motion_distance" +CONF_CUSTOM_SPATIAL_STATIC_VALUE = "custom_spatial_static_value" +CONF_CUSTOM_SPATIAL_MOTION_VALUE = "custom_spatial_motion_value" +CONF_CUSTOM_MOTION_SPEED = "custom_motion_speed" +CONF_CUSTOM_MODE_NUM = "custom_mode_num" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_CUSTOM_PRESENCE_OF_DETECTION): sensor.sensor_schema( + device_class=DEVICE_CLASS_DISTANCE, + unit_of_measurement=UNIT_METER, + accuracy_decimals=2, # Specify the number of decimal places + icon="mdi:signal-distance-variant", + ), + cv.Optional(CONF_MOVEMENT_SIGNS): sensor.sensor_schema( + icon="mdi:human-greeting-variant", + ), + cv.Optional(CONF_CUSTOM_MOTION_DISTANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_METER, + accuracy_decimals=2, + icon="mdi:signal-distance-variant", + ), + cv.Optional(CONF_CUSTOM_SPATIAL_STATIC_VALUE): sensor.sensor_schema( + device_class=DEVICE_CLASS_ENERGY, + icon="mdi:counter", + ), + cv.Optional(CONF_CUSTOM_SPATIAL_MOTION_VALUE): sensor.sensor_schema( + device_class=DEVICE_CLASS_ENERGY, + icon="mdi:counter", + ), + cv.Optional(CONF_CUSTOM_MOTION_SPEED): sensor.sensor_schema( + unit_of_measurement="m/s", + device_class=DEVICE_CLASS_SPEED, + accuracy_decimals=2, + icon="mdi:run-fast", + ), + cv.Optional(CONF_CUSTOM_MODE_NUM): sensor.sensor_schema( + icon="mdi:counter", + ), + } +) + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if custompresenceofdetection_config := config.get( + CONF_CUSTOM_PRESENCE_OF_DETECTION + ): + sens = await sensor.new_sensor(custompresenceofdetection_config) + cg.add(mr24hpc1_component.set_custom_presence_of_detection_sensor(sens)) + if movementsigns_config := config.get(CONF_MOVEMENT_SIGNS): + sens = await sensor.new_sensor(movementsigns_config) + cg.add(mr24hpc1_component.set_movement_signs_sensor(sens)) + if custommotiondistance_config := config.get(CONF_CUSTOM_MOTION_DISTANCE): + sens = await sensor.new_sensor(custommotiondistance_config) + cg.add(mr24hpc1_component.set_custom_motion_distance_sensor(sens)) + if customspatialstaticvalue_config := config.get(CONF_CUSTOM_SPATIAL_STATIC_VALUE): + sens = await sensor.new_sensor(customspatialstaticvalue_config) + cg.add(mr24hpc1_component.set_custom_spatial_static_value_sensor(sens)) + if customspatialmotionvalue_config := config.get(CONF_CUSTOM_SPATIAL_MOTION_VALUE): + sens = await sensor.new_sensor(customspatialmotionvalue_config) + cg.add(mr24hpc1_component.set_custom_spatial_motion_value_sensor(sens)) + if custommotionspeed_config := config.get(CONF_CUSTOM_MOTION_SPEED): + sens = await sensor.new_sensor(custommotionspeed_config) + cg.add(mr24hpc1_component.set_custom_motion_speed_sensor(sens)) + if custommodenum_config := config.get(CONF_CUSTOM_MODE_NUM): + sens = await sensor.new_sensor(custommodenum_config) + cg.add(mr24hpc1_component.set_custom_mode_num_sensor(sens)) diff --git a/esphome/components/seeed_mr24hpc1/switch/__init__.py b/esphome/components/seeed_mr24hpc1/switch/__init__.py new file mode 100644 index 0000000000..bbf5391a57 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/switch/__init__.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_SWITCH, + ENTITY_CATEGORY_CONFIG, +) +from .. import CONF_MR24HPC1_ID, MR24HPC1Component, mr24hpc1_ns + +UnderlyingOpenFuncSwitch = mr24hpc1_ns.class_( + "UnderlyOpenFunctionSwitch", switch.Switch +) + +CONF_UNDERLYING_OPEN_FUNCTION = "underlying_open_function" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_UNDERLYING_OPEN_FUNCTION): switch.switch_schema( + UnderlyingOpenFuncSwitch, + device_class=DEVICE_CLASS_SWITCH, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:electric-switch", + ), +} + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if underlying_open_function_config := config.get(CONF_UNDERLYING_OPEN_FUNCTION): + s = await switch.new_switch(underlying_open_function_config) + await cg.register_parented(s, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_underlying_open_function_switch(s)) diff --git a/esphome/components/seeed_mr24hpc1/switch/underlyFuc_switch.cpp b/esphome/components/seeed_mr24hpc1/switch/underlyFuc_switch.cpp new file mode 100644 index 0000000000..0fcc49bc4c --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/switch/underlyFuc_switch.cpp @@ -0,0 +1,12 @@ +#include "underlyFuc_switch.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void UnderlyOpenFunctionSwitch::write_state(bool state) { + this->publish_state(state); + this->parent_->set_underlying_open_function(state); +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/switch/underlyFuc_switch.h b/esphome/components/seeed_mr24hpc1/switch/underlyFuc_switch.h new file mode 100644 index 0000000000..1baabb25ce --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/switch/underlyFuc_switch.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/switch/switch.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class UnderlyOpenFunctionSwitch : public switch_::Switch, public Parented { + public: + UnderlyOpenFunctionSwitch() = default; + + protected: + void write_state(bool state) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/text_sensor.py b/esphome/components/seeed_mr24hpc1/text_sensor.py new file mode 100644 index 0000000000..aa50f577d4 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/text_sensor.py @@ -0,0 +1,74 @@ +import esphome.codegen as cg +from esphome.components import text_sensor +import esphome.config_validation as cv +from esphome.const import ENTITY_CATEGORY_DIAGNOSTIC +from . import CONF_MR24HPC1_ID, MR24HPC1Component + +CONF_HEART_BEAT = "heart_beat" +CONF_PRODUCT_MODEL = "product_model" +CONF_PRODUCT_ID = "product_id" +CONF_HARDWARE_MODEL = "hardware_model" +CONF_HARDWARE_VERSION = "hardware_version" + +CONF_KEEP_AWAY = "keep_away" +CONF_MOTION_STATUS = "motion_status" + +CONF_CUSTOM_MODE_END = "custom_mode_end" + + +# The entity category for read only diagnostic values, for example RSSI, uptime or MAC Address +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_HEART_BEAT): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:connection" + ), + cv.Optional(CONF_PRODUCT_MODEL): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:information-outline" + ), + cv.Optional(CONF_PRODUCT_ID): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:information-outline" + ), + cv.Optional(CONF_HARDWARE_MODEL): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:information-outline" + ), + cv.Optional(CONF_HARDWARE_VERSION): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:information-outline" + ), + cv.Optional(CONF_KEEP_AWAY): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:walk" + ), + cv.Optional(CONF_MOTION_STATUS): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:human-greeting" + ), + cv.Optional(CONF_CUSTOM_MODE_END): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:account-check" + ), +} + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if heartbeat_config := config.get(CONF_HEART_BEAT): + sens = await text_sensor.new_text_sensor(heartbeat_config) + cg.add(mr24hpc1_component.set_heartbeat_state_text_sensor(sens)) + if productmodel_config := config.get(CONF_PRODUCT_MODEL): + sens = await text_sensor.new_text_sensor(productmodel_config) + cg.add(mr24hpc1_component.set_product_model_text_sensor(sens)) + if productid_config := config.get(CONF_PRODUCT_ID): + sens = await text_sensor.new_text_sensor(productid_config) + cg.add(mr24hpc1_component.set_product_id_text_sensor(sens)) + if hardwaremodel_config := config.get(CONF_HARDWARE_MODEL): + sens = await text_sensor.new_text_sensor(hardwaremodel_config) + cg.add(mr24hpc1_component.set_hardware_model_text_sensor(sens)) + if firwareversion_config := config.get(CONF_HARDWARE_VERSION): + sens = await text_sensor.new_text_sensor(firwareversion_config) + cg.add(mr24hpc1_component.set_firware_version_text_sensor(sens)) + if keepaway_config := config.get(CONF_KEEP_AWAY): + sens = await text_sensor.new_text_sensor(keepaway_config) + cg.add(mr24hpc1_component.set_keep_away_text_sensor(sens)) + if motionstatus_config := config.get(CONF_MOTION_STATUS): + sens = await text_sensor.new_text_sensor(motionstatus_config) + cg.add(mr24hpc1_component.set_motion_status_text_sensor(sens)) + if custommodeend_config := config.get(CONF_CUSTOM_MODE_END): + sens = await text_sensor.new_text_sensor(custommodeend_config) + cg.add(mr24hpc1_component.set_custom_mode_end_text_sensor(sens)) diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index c90880bc9f..0efc961943 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -352,7 +352,7 @@ void SEN5XComponent::update() { float humidity = measurements[4] / 100.0; if (measurements[4] == 0xFFFF) humidity = NAN; - float temperature = measurements[5] / 200.0; + float temperature = (int16_t) measurements[5] / 200.0; if (measurements[5] == 0xFFFF) temperature = NAN; float voc = measurements[6] / 10.0; diff --git a/esphome/components/sen5x/sensor.py b/esphome/components/sen5x/sensor.py index 4bc4a138a3..67bd627f7f 100644 --- a/esphome/components/sen5x/sensor.py +++ b/esphome/components/sen5x/sensor.py @@ -14,13 +14,12 @@ from esphome.const import ( CONF_PM_4_0, CONF_STORE_BASELINE, CONF_TEMPERATURE, + DEVICE_CLASS_AQI, DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_NITROUS_OXIDE, DEVICE_CLASS_PM1, DEVICE_CLASS_PM10, DEVICE_CLASS_PM25, DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, ICON_CHEMICAL_WEAPON, ICON_RADIATOR, ICON_THERMOMETER, @@ -132,13 +131,13 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_VOC): sensor.sensor_schema( icon=ICON_RADIATOR, accuracy_decimals=0, - device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + device_class=DEVICE_CLASS_AQI, state_class=STATE_CLASS_MEASUREMENT, ).extend(GAS_SENSOR), cv.Optional(CONF_NOX): sensor.sensor_schema( icon=ICON_RADIATOR, accuracy_decimals=0, - device_class=DEVICE_CLASS_NITROUS_OXIDE, + device_class=DEVICE_CLASS_AQI, state_class=STATE_CLASS_MEASUREMENT, ).extend(GAS_SENSOR), cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean, diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp index e0504eb2b9..e58ee157f7 100644 --- a/esphome/components/senseair/senseair.cpp +++ b/esphome/components/senseair/senseair.cpp @@ -54,9 +54,9 @@ void SenseAirComponent::update() { this->status_clear_warning(); const uint8_t length = response[2]; const uint16_t status = (uint16_t(response[3]) << 8) | response[4]; - const uint16_t ppm = (uint16_t(response[length + 1]) << 8) | response[length + 2]; + const int16_t ppm = int16_t((response[length + 1] << 8) | response[length + 2]); - ESP_LOGD(TAG, "SenseAir Received CO₂=%uppm Status=0x%02X", ppm, status); + ESP_LOGD(TAG, "SenseAir Received CO₂=%dppm Status=0x%02X", ppm, status); if (this->co2_sensor_ != nullptr) this->co2_sensor_->publish_state(ppm); } diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index b3bf533695..46295fe958 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -82,6 +82,7 @@ from esphome.const import ( DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLUME, + DEVICE_CLASS_VOLUME_FLOW_RATE, DEVICE_CLASS_VOLUME_STORAGE, DEVICE_CLASS_WATER, DEVICE_CLASS_WEIGHT, @@ -141,6 +142,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLUME, + DEVICE_CLASS_VOLUME_FLOW_RATE, DEVICE_CLASS_VOLUME_STORAGE, DEVICE_CLASS_WATER, DEVICE_CLASS_WEIGHT, diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index b5bef4930c..6d7acad7e0 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -252,7 +252,9 @@ ThrottleAverageFilter::ThrottleAverageFilter(uint32_t time_period) : time_period optional ThrottleAverageFilter::new_value(float value) { ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::new_value(value=%f)", this, value); - if (!std::isnan(value)) { + if (std::isnan(value)) { + this->have_nan_ = true; + } else { this->sum_ += value; this->n_++; } @@ -262,12 +264,14 @@ void ThrottleAverageFilter::setup() { this->set_interval("throttle_average", this->time_period_, [this]() { ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::interval(sum=%f, n=%i)", this, this->sum_, this->n_); if (this->n_ == 0) { - this->output(NAN); + if (this->have_nan_) + this->output(NAN); } else { this->output(this->sum_ / this->n_); this->sum_ = 0.0f; this->n_ = 0; } + this->have_nan_ = false; }); } float ThrottleAverageFilter::get_setup_priority() const { return setup_priority::HARDWARE; } diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index fa78f2fa46..1a08699d7b 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -245,6 +245,7 @@ class ThrottleAverageFilter : public Filter, public Component { uint32_t time_period_; float sum_{0.0f}; unsigned int n_{0}; + bool have_nan_{false}; }; using lambda_filter_t = std::function(float)>; diff --git a/esphome/components/servo/servo.cpp b/esphome/components/servo/servo.cpp index 666c017dea..18e8c8087e 100644 --- a/esphome/components/servo/servo.cpp +++ b/esphome/components/servo/servo.cpp @@ -19,13 +19,28 @@ void Servo::dump_config() { ESP_LOGCONFIG(TAG, " run duration: %" PRIu32 " ms", this->transition_length_); } +void Servo::setup() { + float v; + if (this->restore_) { + this->rtc_ = global_preferences->make_preference(global_servo_id); + global_servo_id++; + if (this->rtc_.load(&v)) { + this->target_value_ = v; + this->internal_write(v); + this->state_ = STATE_ATTACHED; + this->start_millis_ = millis(); + return; + } + } + this->detach(); +} + void Servo::loop() { // check if auto_detach_time_ is set and servo reached target if (this->auto_detach_time_ && this->state_ == STATE_TARGET_REACHED) { if (millis() - this->start_millis_ > this->auto_detach_time_) { this->detach(); this->start_millis_ = 0; - this->state_ = STATE_DETACHED; ESP_LOGD(TAG, "Servo detached on auto_detach_time"); } } @@ -54,8 +69,11 @@ void Servo::loop() { void Servo::write(float value) { value = clamp(value, -1.0f, 1.0f); - if (this->target_value_ == value) + if ((this->state_ == STATE_DETACHED) && (this->target_value_ == value)) { this->internal_write(value); + } else { + this->save_level_(value); + } this->target_value_ = value; this->source_value_ = this->current_value_; this->state_ = STATE_ATTACHED; @@ -72,11 +90,18 @@ void Servo::internal_write(float value) { level = lerp(value, this->idle_level_, this->max_level_); } this->output_->set_level(level); - if (this->target_value_ == this->current_value_) { - this->save_level_(level); - } this->current_value_ = value; } +void Servo::detach() { + this->state_ = STATE_DETACHED; + this->output_->set_level(0.0f); +} + +void Servo::save_level_(float v) { + if (this->restore_) + this->rtc_.save(&v); +} + } // namespace servo } // namespace esphome diff --git a/esphome/components/servo/servo.h b/esphome/components/servo/servo.h index e2e3823158..13a7472ae5 100644 --- a/esphome/components/servo/servo.h +++ b/esphome/components/servo/servo.h @@ -17,22 +17,8 @@ class Servo : public Component { void loop() override; void write(float value); void internal_write(float value); - void detach() { - this->output_->set_level(0.0f); - this->save_level_(0.0f); - } - void setup() override { - float v; - if (this->restore_) { - this->rtc_ = global_preferences->make_preference(global_servo_id); - global_servo_id++; - if (this->rtc_.load(&v)) { - this->output_->set_level(v); - return; - } - } - this->detach(); - } + void detach(); + void setup() override; void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } void set_min_level(float min_level) { min_level_ = min_level; } @@ -42,8 +28,10 @@ class Servo : public Component { void set_auto_detach_time(uint32_t auto_detach_time) { auto_detach_time_ = auto_detach_time; } void set_transition_length(uint32_t transition_length) { transition_length_ = transition_length; } + bool has_reached_target() { return this->current_value_ == this->target_value_; } + protected: - void save_level_(float v) { this->rtc_.save(&v); } + void save_level_(float v); output::FloatOutput *output_; float min_level_ = 0.0300f; diff --git a/esphome/components/shelly_dimmer/light.py b/esphome/components/shelly_dimmer/light.py index 5bdb54baf5..625784427f 100644 --- a/esphome/components/shelly_dimmer/light.py +++ b/esphome/components/shelly_dimmer/light.py @@ -29,7 +29,8 @@ from esphome.const import ( from esphome.core import HexInt, CORE DOMAIN = "shelly_dimmer" -DEPENDENCIES = ["sensor", "uart", "esp8266"] +AUTO_LOAD = ["sensor"] +DEPENDENCIES = ["uart", "esp8266"] shelly_dimmer_ns = cg.esphome_ns.namespace("shelly_dimmer") ShellyDimmer = shelly_dimmer_ns.class_( diff --git a/esphome/components/sht3xd/sensor.py b/esphome/components/sht3xd/sensor.py index 80e15a1ab9..1286489b29 100644 --- a/esphome/components/sht3xd/sensor.py +++ b/esphome/components/sht3xd/sensor.py @@ -14,6 +14,8 @@ from esphome.const import ( CONF_HEATER_ENABLED = "heater_enabled" +CODEOWNERS = ["@mrtoy-me"] + DEPENDENCIES = ["i2c"] AUTO_LOAD = ["sensirion_common"] @@ -26,13 +28,13 @@ CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(SHT3XDComponent), - cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ), - cv.Required(CONF_HUMIDITY): sensor.sensor_schema( + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, accuracy_decimals=1, device_class=DEVICE_CLASS_HUMIDITY, diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index 25332165c0..888e954c6b 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -6,7 +6,11 @@ namespace sht3xd { static const char *const TAG = "sht3xd"; -static const uint16_t SHT3XD_COMMAND_READ_SERIAL_NUMBER = 0x3780; +// use read serial number register with clock stretching disabled as per other SHT3XD_COMMAND registers +// which provides support for SHT85 sensor +// SHT85 does not support clock stretching and uses same registers as SHT3xd with clock stretching disabled +static const uint16_t SHT3XD_COMMAND_READ_SERIAL_NUMBER = 0x3682; + static const uint16_t SHT3XD_COMMAND_READ_STATUS = 0xF32D; static const uint16_t SHT3XD_COMMAND_CLEAR_STATUS = 0x3041; static const uint16_t SHT3XD_COMMAND_HEATER_ENABLE = 0x306D; @@ -22,25 +26,32 @@ void SHT3XDComponent::setup() { this->mark_failed(); return; } + this->serial_number_ = (uint32_t(raw_serial_number[0]) << 16) | uint32_t(raw_serial_number[1]); + if (!this->write_command(heater_enabled_ ? SHT3XD_COMMAND_HEATER_ENABLE : SHT3XD_COMMAND_HEATER_DISABLE)) { this->mark_failed(); return; } - uint32_t serial_number = (uint32_t(raw_serial_number[0]) << 16) | uint32_t(raw_serial_number[1]); - ESP_LOGV(TAG, " Serial Number: 0x%08" PRIX32, serial_number); } + void SHT3XDComponent::dump_config() { ESP_LOGCONFIG(TAG, "SHT3xD:"); - LOG_I2C_DEVICE(this); if (this->is_failed()) { - ESP_LOGE(TAG, "Communication with SHT3xD failed!"); + ESP_LOGE(TAG, " Communication with SHT3xD failed!"); + return; } + ESP_LOGD(TAG, " Serial Number: 0x%08" PRIX32, this->serial_number_); + ESP_LOGD(TAG, " Heater Enabled: %s", this->heater_enabled_ ? "true" : "false"); + + LOG_I2C_DEVICE(this); LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); } + float SHT3XDComponent::get_setup_priority() const { return setup_priority::DATA; } + void SHT3XDComponent::update() { if (this->status_has_warning()) { ESP_LOGD(TAG, "Retrying to reconnect the sensor."); diff --git a/esphome/components/sht3xd/sht3xd.h b/esphome/components/sht3xd/sht3xd.h index 4133bf7b93..d1a3360e69 100644 --- a/esphome/components/sht3xd/sht3xd.h +++ b/esphome/components/sht3xd/sht3xd.h @@ -25,6 +25,7 @@ class SHT3XDComponent : public PollingComponent, public sensirion_common::Sensir sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; bool heater_enabled_{true}; + uint32_t serial_number_{0}; }; } // namespace sht3xd diff --git a/esphome/components/sm2135/__init__.py b/esphome/components/sm2135/__init__.py index ce78d5337f..52128f1f24 100644 --- a/esphome/components/sm2135/__init__.py +++ b/esphome/components/sm2135/__init__.py @@ -15,6 +15,7 @@ SM2135 = sm2135_ns.class_("SM2135", cg.Component) CONF_RGB_CURRENT = "rgb_current" CONF_CW_CURRENT = "cw_current" +CONF_SEPARATE_MODES = "separate_modes" SM2135Current = sm2135_ns.enum("SM2135Current") @@ -51,6 +52,7 @@ CONFIG_SCHEMA = cv.Schema( cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RGB_CURRENT, "20mA"): cv.enum(DRIVE_STRENGTHS_RGB), cv.Optional(CONF_CW_CURRENT, "10mA"): cv.enum(DRIVE_STRENGTHS_CW), + cv.Optional(CONF_SEPARATE_MODES, default=True): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -66,3 +68,4 @@ async def to_code(config): cg.add(var.set_rgb_current(config[CONF_RGB_CURRENT])) cg.add(var.set_cw_current(config[CONF_CW_CURRENT])) + cg.add(var.set_separate_modes(config[CONF_SEPARATE_MODES])) diff --git a/esphome/components/sm2135/sm2135.cpp b/esphome/components/sm2135/sm2135.cpp index f9cd4235ed..9a576859ac 100644 --- a/esphome/components/sm2135/sm2135.cpp +++ b/esphome/components/sm2135/sm2135.cpp @@ -97,23 +97,32 @@ void SM2135::loop() { this->write_byte_(SM2135_ADDR_MC); this->write_byte_(current_mask_); - if (this->update_channel_ == 3 || this->update_channel_ == 4) { - // No color so must be Cold/Warm + if (this->separate_modes_) { + if (this->update_channel_ == 3 || this->update_channel_ == 4) { + // No color so must be Cold/Warm - this->write_byte_(SM2135_CW); - this->sm2135_stop_(); - delay(1); - this->sm2135_start_(); - this->write_byte_(SM2135_ADDR_C); - this->write_byte_(this->pwm_amounts_[4]); // Warm - this->write_byte_(this->pwm_amounts_[3]); // Cold + this->write_byte_(SM2135_CW); + this->sm2135_stop_(); + delay(1); + this->sm2135_start_(); + this->write_byte_(SM2135_ADDR_C); + this->write_byte_(this->pwm_amounts_[4]); // Warm + this->write_byte_(this->pwm_amounts_[3]); // Cold + } else { + // Color + + this->write_byte_(SM2135_RGB); + this->write_byte_(this->pwm_amounts_[1]); // Green + this->write_byte_(this->pwm_amounts_[0]); // Red + this->write_byte_(this->pwm_amounts_[2]); // Blue + } } else { - // Color - this->write_byte_(SM2135_RGB); this->write_byte_(this->pwm_amounts_[1]); // Green this->write_byte_(this->pwm_amounts_[0]); // Red this->write_byte_(this->pwm_amounts_[2]); // Blue + this->write_byte_(this->pwm_amounts_[4]); // Warm + this->write_byte_(this->pwm_amounts_[3]); // Cold } this->sm2135_stop_(); diff --git a/esphome/components/sm2135/sm2135.h b/esphome/components/sm2135/sm2135.h index a557fc3287..6f207d093a 100644 --- a/esphome/components/sm2135/sm2135.h +++ b/esphome/components/sm2135/sm2135.h @@ -39,6 +39,8 @@ class SM2135 : public Component { this->current_mask_ = (this->rgb_current_ << 4) | this->cw_current_; } + void set_separate_modes(bool separate_modes) { this->separate_modes_ = separate_modes; } + void setup() override; void dump_config() override; @@ -78,6 +80,7 @@ class SM2135 : public Component { uint8_t current_mask_; SM2135Current rgb_current_; SM2135Current cw_current_; + bool separate_modes_; uint8_t update_channel_; std::vector pwm_amounts_; bool update_{true}; diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 418eacd870..6a60e8d5c1 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -25,6 +25,7 @@ namespace sntp { static const char *const TAG = "sntp"; void SNTPComponent::setup() { +#ifndef USE_HOST ESP_LOGCONFIG(TAG, "Setting up SNTP..."); #if defined(USE_ESP32) || defined(USE_LIBRETINY) if (sntp_enabled()) { @@ -48,6 +49,7 @@ void SNTPComponent::setup() { #endif sntp_init(); +#endif } void SNTPComponent::dump_config() { ESP_LOGCONFIG(TAG, "SNTP Time:"); @@ -57,7 +59,7 @@ void SNTPComponent::dump_config() { ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); } void SNTPComponent::update() { -#ifndef USE_ESP_IDF +#if !defined(USE_ESP_IDF) && !defined(USE_HOST) // force resync if (sntp_enabled()) { sntp_stop(); diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 5d44cd7689..f07f5c8f81 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -86,6 +86,13 @@ class BSDSocketImpl : public Socket { } int listen(int backlog) override { return ::listen(fd_, backlog); } ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); } + ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override { +#if defined(USE_ESP32) || defined(USE_HOST) + return ::recvfrom(this->fd_, buf, len, 0, addr, addr_len); +#else + return ::lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len); +#endif + } ssize_t readv(const struct iovec *iov, int iovcnt) override { #if defined(USE_ESP32) return ::lwip_readv(fd_, iov, iovcnt); diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp index d0fce9198f..b200046d7f 100644 --- a/esphome/components/socket/socket.cpp +++ b/esphome/components/socket/socket.cpp @@ -10,15 +10,15 @@ namespace socket { Socket::~Socket() {} std::unique_ptr socket_ip(int type, int protocol) { -#if ENABLE_IPV6 +#if USE_NETWORK_IPV6 return socket(AF_INET6, type, protocol); #else return socket(AF_INET, type, protocol); -#endif +#endif /* USE_NETWORK_IPV6 */ } socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::string &ip_address, uint16_t port) { -#if ENABLE_IPV6 +#if USE_NETWORK_IPV6 if (addrlen < sizeof(sockaddr_in6)) { errno = EINVAL; return 0; @@ -47,11 +47,11 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::stri server->sin_addr.s_addr = inet_addr(ip_address.c_str()); server->sin_port = htons(port); return sizeof(sockaddr_in); -#endif +#endif /* USE_NETWORK_IPV6 */ } socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port) { -#if ENABLE_IPV6 +#if USE_NETWORK_IPV6 if (addrlen < sizeof(sockaddr_in6)) { errno = EINVAL; return 0; @@ -73,7 +73,7 @@ socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t po server->sin_addr.s_addr = ESPHOME_INADDR_ANY; server->sin_port = htons(port); return sizeof(sockaddr_in); -#endif +#endif /* USE_NETWORK_IPV6 */ } } // namespace socket } // namespace esphome diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index c9b8be88a0..5c12210d15 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -31,6 +31,9 @@ class Socket { virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0; virtual int listen(int backlog) = 0; virtual ssize_t read(void *buf, size_t len) = 0; +#ifdef USE_SOCKET_IMPL_BSD_SOCKETS + virtual ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) = 0; +#endif virtual ssize_t readv(const struct iovec *iov, int iovcnt) = 0; virtual ssize_t write(const void *buf, size_t len) = 0; virtual ssize_t writev(const struct iovec *iov, int iovcnt) = 0; diff --git a/esphome/components/speed/fan/__init__.py b/esphome/components/speed/fan/__init__.py index 3acfb005bd..5fbf011669 100644 --- a/esphome/components/speed/fan/__init__.py +++ b/esphome/components/speed/fan/__init__.py @@ -32,11 +32,13 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( async def to_code(config): - output_ = await cg.get_variable(config[CONF_OUTPUT]) - var = cg.new_Pvariable(config[CONF_OUTPUT_ID], output_, config[CONF_SPEED_COUNT]) + var = cg.new_Pvariable(config[CONF_OUTPUT_ID], config[CONF_SPEED_COUNT]) 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_)) + if CONF_OSCILLATION_OUTPUT in config: oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) cg.add(var.set_oscillating(oscillation_output)) diff --git a/esphome/components/speed/fan/speed_fan.cpp b/esphome/components/speed/fan/speed_fan.cpp index 41b222acd6..57bd795416 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -38,7 +38,6 @@ void SpeedFan::control(const fan::FanCall &call) { void SpeedFan::write_state_() { float speed = this->state ? static_cast(this->speed) / static_cast(this->speed_count_) : 0.0f; this->output_->set_level(speed); - if (this->oscillating_ != nullptr) this->oscillating_->set_state(this->oscillating); if (this->direction_ != nullptr) diff --git a/esphome/components/speed/fan/speed_fan.h b/esphome/components/speed/fan/speed_fan.h index ca0fe20e2a..6537bce3f6 100644 --- a/esphome/components/speed/fan/speed_fan.h +++ b/esphome/components/speed/fan/speed_fan.h @@ -12,9 +12,10 @@ namespace speed { class SpeedFan : public Component, public fan::Fan { public: - SpeedFan(output::FloatOutput *output, int speed_count) : output_(output), speed_count_(speed_count) {} + SpeedFan(int speed_count) : speed_count_(speed_count) {} void setup() override; void dump_config() override; + void set_output(output::FloatOutput *output) { this->output_ = output; } void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; } void set_preset_modes(const std::set &presets) { this->preset_modes_ = presets; } diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index d116641373..fdf19bb56e 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -29,12 +29,17 @@ from esphome.const import ( PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, + CONF_DATA_PINS, +) +from esphome.core import ( + coroutine_with_priority, + CORE, ) -from esphome.core import coroutine_with_priority, CORE CODEOWNERS = ["@esphome/core", "@clydebarrow"] spi_ns = cg.esphome_ns.namespace("spi") SPIComponent = spi_ns.class_("SPIComponent", cg.Component) +QuadSPIComponent = spi_ns.class_("QuadSPIComponent", cg.Component) SPIDevice = spi_ns.class_("SPIDevice") SPIDataRate = spi_ns.enum("SPIDataRate") SPIMode = spi_ns.enum("SPIMode") @@ -70,8 +75,11 @@ CONF_SPI_MODE = "spi_mode" CONF_FORCE_SW = "force_sw" CONF_INTERFACE = "interface" CONF_INTERFACE_INDEX = "interface_index" +TYPE_SINGLE = "single" +TYPE_QUAD = "quad" -# RP2040 SPI pin assignments are complicated. Refer to https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf +# RP2040 SPI pin assignments are complicated; +# refer to GPIO function select table in https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf RP_SPI_PINSETS = [ { @@ -82,7 +90,7 @@ RP_SPI_PINSETS = [ { CONF_MISO_PIN: [8, 12, 24, 28, -1], CONF_CLK_PIN: [10, 14, 26], - CONF_MOSI_PIN: [11, 23, 27, -1], + CONF_MOSI_PIN: [11, 15, 27, -1], }, ] @@ -191,11 +199,6 @@ def validate_spi_config(config): available = list(range(len(get_hw_interface_list()))) for spi in config: interface = spi[CONF_INTERFACE] - if spi[CONF_FORCE_SW]: - if interface == "any": - spi[CONF_INTERFACE] = interface = "software" - elif interface != "software": - raise cv.Invalid("force_sw is deprecated - use interface: software") if interface == "software": pass elif interface == "any": @@ -229,6 +232,8 @@ def validate_spi_config(config): spi, spi[CONF_INTERFACE_INDEX] ): raise cv.Invalid("Invalid pin selections for hardware SPI interface") + if CONF_DATA_PINS in spi and CONF_INTERFACE_INDEX not in spi: + raise cv.Invalid("Quad mode requires a hardware interface") return config @@ -256,19 +261,57 @@ SPI_SCHEMA = cv.All( cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_FORCE_SW, default=False): cv.boolean, + cv.Optional(CONF_FORCE_SW): cv.invalid( + "force_sw is deprecated - use interface: software" + ), cv.Optional(CONF_INTERFACE, default="any"): cv.one_of( *sum(get_hw_interface_list(), ["software", "hardware", "any"]), lower=True, ), + cv.Optional(CONF_DATA_PINS): cv.invalid( + "'data_pins' should be used with 'type: quad' only" + ), } ), cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]), ) +SPI_QUAD_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(QuadSPIComponent), + cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_DATA_PINS): cv.All( + cv.ensure_list(pins.internal_gpio_output_pin_number), + cv.Length(min=4, max=4), + ), + cv.Optional(CONF_INTERFACE, default="hardware"): cv.one_of( + *sum(get_hw_interface_list(), ["hardware"]), + lower=True, + ), + cv.Optional(CONF_MISO_PIN): cv.invalid( + "'miso_pin' should not be used with quad SPI" + ), + cv.Optional(CONF_MOSI_PIN): cv.invalid( + "'mosi_pin' should not be used with quad SPI" + ), + } + ), + cv.only_on([PLATFORM_ESP32]), + cv.only_with_esp_idf, +) + CONFIG_SCHEMA = cv.All( - cv.ensure_list(SPI_SCHEMA), + cv.ensure_list( + cv.typed_schema( + { + TYPE_SINGLE: SPI_SCHEMA, + TYPE_QUAD: SPI_QUAD_SCHEMA, + }, + default_type=TYPE_SINGLE, + ) + ), validate_spi_config, ) @@ -277,43 +320,46 @@ CONFIG_SCHEMA = cv.All( async def to_code(configs): cg.add_define("USE_SPI") cg.add_global(spi_ns.using) + if CORE.using_arduino: + cg.add_library("SPI", None) for spi in configs: var = cg.new_Pvariable(spi[CONF_ID]) await cg.register_component(var, spi) - clk = await cg.gpio_pin_expression(spi[CONF_CLK_PIN]) cg.add(var.set_clk(clk)) - if CONF_MISO_PIN in spi: - miso = await cg.gpio_pin_expression(spi[CONF_MISO_PIN]) - cg.add(var.set_miso(miso)) - if CONF_MOSI_PIN in spi: - mosi = await cg.gpio_pin_expression(spi[CONF_MOSI_PIN]) - cg.add(var.set_mosi(mosi)) - if CONF_INTERFACE_INDEX in spi: - index = spi[CONF_INTERFACE_INDEX] - cg.add(var.set_interface(cg.RawExpression(get_spi_interface(index)))) + if miso := spi.get(CONF_MISO_PIN): + cg.add(var.set_miso(await cg.gpio_pin_expression(miso))) + if mosi := spi.get(CONF_MOSI_PIN): + cg.add(var.set_mosi(await cg.gpio_pin_expression(mosi))) + if data_pins := spi.get(CONF_DATA_PINS): + cg.add(var.set_data_pins(data_pins)) + if (index := spi.get(CONF_INTERFACE_INDEX)) is not None: + interface = get_spi_interface(index) + cg.add(var.set_interface(cg.RawExpression(interface))) cg.add( var.set_interface_name( - re.sub( - r"\W", "", get_spi_interface(index).replace("new SPIClass", "") - ) + re.sub(r"\W", "", interface.replace("new SPIClass", "")) ) ) - if CORE.using_arduino: - cg.add_library("SPI", None) - def spi_device_schema( - cs_pin_required=True, default_data_rate=cv.UNDEFINED, default_mode=cv.UNDEFINED + cs_pin_required=True, + default_data_rate=cv.UNDEFINED, + default_mode=cv.UNDEFINED, + quad=False, ): """Create a schema for an SPI device. :param cs_pin_required: If true, make the CS_PIN required in the config. :param default_data_rate: Optional data_rate to use as default + :param default_mode Optional. The default SPI mode to use. + :param quad If set, will require an SPI component configured as quad data bits. :return: The SPI device schema, `extend` this in your config schema. """ schema = { - cv.GenerateID(CONF_SPI_ID): cv.use_id(SPIComponent), + cv.GenerateID(CONF_SPI_ID): cv.use_id( + QuadSPIComponent if quad else SPIComponent + ), cv.Optional(CONF_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA, cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum( SPI_MODE_OPTIONS, upper=True diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 9d06ac0e45..b13826c443 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -49,7 +49,8 @@ void SPIComponent::setup() { } if (this->using_hw_) { - this->spi_bus_ = SPIComponent::get_bus(this->interface_, this->clk_pin_, this->sdo_pin_, this->sdi_pin_); + this->spi_bus_ = + SPIComponent::get_bus(this->interface_, this->clk_pin_, this->sdo_pin_, this->sdi_pin_, this->data_pins_); if (this->spi_bus_ == nullptr) { ESP_LOGE(TAG, "Unable to allocate SPI interface"); this->mark_failed(); @@ -68,6 +69,9 @@ void SPIComponent::dump_config() { LOG_PIN(" CLK Pin: ", this->clk_pin_) LOG_PIN(" SDI Pin: ", this->sdi_pin_) LOG_PIN(" SDO Pin: ", this->sdo_pin_) + for (size_t i = 0; i != this->data_pins_.size(); i++) { + ESP_LOGCONFIG(TAG, " Data pin %u: GPIO%d", i, this->data_pins_[i]); + } if (this->spi_bus_->is_hw()) { ESP_LOGCONFIG(TAG, " Using HW SPI: %s", this->interface_name_); } else { diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 0eb4cd7eb6..f581dc3f56 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -1,11 +1,12 @@ #pragma once +#include "esphome/core/application.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" -#include "esphome/core/application.h" -#include #include +#include +#include #ifdef USE_ARDUINO @@ -208,6 +209,10 @@ class SPIDelegate { esph_log_e("spi_device", "variable length write not implemented"); } + virtual void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address, + const uint8_t *data, size_t length, uint8_t bus_width) { + esph_log_e("spi_device", "write_cmd_addr_data not implemented"); + } // write 16 bits virtual void write16(uint16_t data) { if (this->bit_order_ == BIT_ORDER_MSB_FIRST) { @@ -331,6 +336,7 @@ class SPIComponent : public Component { void set_miso(GPIOPin *sdi) { this->sdi_pin_ = sdi; } void set_mosi(GPIOPin *sdo) { this->sdo_pin_ = sdo; } + void set_data_pins(std::vector pins) { this->data_pins_ = std::move(pins); } void set_interface(SPIInterface interface) { this->interface_ = interface; @@ -348,15 +354,19 @@ class SPIComponent : public Component { GPIOPin *clk_pin_{nullptr}; GPIOPin *sdi_pin_{nullptr}; GPIOPin *sdo_pin_{nullptr}; + std::vector data_pins_{}; + SPIInterface interface_{}; bool using_hw_{false}; const char *interface_name_{nullptr}; SPIBus *spi_bus_{}; std::map devices_; - static SPIBus *get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi); + static SPIBus *get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, + const std::vector &data_pins); }; +using QuadSPIComponent = SPIComponent; /** * Base class for SPIDevice, un-templated. */ @@ -422,18 +432,49 @@ class SPIDevice : public SPIClient { void read_array(uint8_t *data, size_t length) { return this->delegate_->read_array(data, length); } + /** + * Write a single data item, up to 32 bits. + * @param data The data + * @param num_bits The number of bits to write. The lower num_bits of data will be sent. + */ void write(uint16_t data, size_t num_bits) { this->delegate_->write(data, num_bits); }; + /* Write command, address and data. Command and address will be written as single-bit SPI, + * data phase can be multiple bit (currently only 1 or 4) + * @param cmd_bits Number of bits to write in the command phase + * @param cmd The command value to write + * @param addr_bits Number of bits to write in addr phase + * @param address Address data + * @param data Plain data bytes + * @param length Number of data bytes + * @param bus_width The number of data lines to use for the data phase. + */ + void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address, const uint8_t *data, + size_t length, uint8_t bus_width = 1) { + this->delegate_->write_cmd_addr_data(cmd_bits, cmd, addr_bits, address, data, length, bus_width); + } + void write_byte(uint8_t data) { this->delegate_->write_array(&data, 1); } + /** + * Write the array data, replace with received data. + * @param data + * @param length + */ void transfer_array(uint8_t *data, size_t length) { this->delegate_->transfer(data, length); } uint8_t transfer_byte(uint8_t data) { return this->delegate_->transfer(data); } - // the driver will byte-swap if required. + /** Write 16 bit data. The driver will byte-swap if required. + */ void write_byte16(uint16_t data) { this->delegate_->write16(data); } - // avoid use of this if possible. It's inefficient and ugly. + /** + * Write an array of data as 16 bit values, byte-swapping if required. Use of this should be avoided as + * it is horribly slow. + * @param data + * @param length + */ void write_array16(const uint16_t *data, size_t length) { this->delegate_->write_array16(data, length); } void enable() { this->delegate_->begin_transaction(); } diff --git a/esphome/components/spi/spi_arduino.cpp b/esphome/components/spi/spi_arduino.cpp index 4628486550..f7fe523a33 100644 --- a/esphome/components/spi/spi_arduino.cpp +++ b/esphome/components/spi/spi_arduino.cpp @@ -85,7 +85,8 @@ class SPIBusHw : public SPIBus { bool is_hw() override { return true; } }; -SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) { +SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, + const std::vector &data_pins) { return new SPIBusHw(clk, sdo, sdi, interface); } diff --git a/esphome/components/spi/spi_esp_idf.cpp b/esphome/components/spi/spi_esp_idf.cpp index 03ab298019..55680f72d3 100644 --- a/esphome/components/spi/spi_esp_idf.cpp +++ b/esphome/components/spi/spi_esp_idf.cpp @@ -104,6 +104,60 @@ class SPIDelegateHw : public SPIDelegate { } } + /** + * Write command, address and data + * @param cmd_bits Number of bits to write in the command phase + * @param cmd The command value to write + * @param addr_bits Number of bits to write in addr phase + * @param address Address data + * @param data Remaining data bytes + * @param length Number of data bytes + * @param bus_width The number of data lines to use + */ + void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address, const uint8_t *data, + size_t length, uint8_t bus_width) override { + spi_transaction_ext_t desc = {}; + if (length == 0 && cmd_bits == 0 && addr_bits == 0) { + esph_log_w(TAG, "Nothing to transfer"); + return; + } + desc.base.flags = SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_DUMMY; + if (bus_width == 4) { + desc.base.flags |= SPI_TRANS_MODE_QIO; + } else if (bus_width == 8) { + desc.base.flags |= SPI_TRANS_MODE_OCT; + } + desc.command_bits = cmd_bits; + desc.address_bits = addr_bits; + desc.dummy_bits = 0; + desc.base.rxlength = 0; + desc.base.cmd = cmd; + desc.base.addr = address; + do { + size_t chunk_size = std::min(length, MAX_TRANSFER_SIZE); + if (data != nullptr && chunk_size != 0) { + desc.base.length = chunk_size * 8; + desc.base.tx_buffer = data; + length -= chunk_size; + data += chunk_size; + } else { + length = 0; + desc.base.length = 0; + } + esp_err_t err = spi_device_polling_start(this->handle_, (spi_transaction_t *) &desc, portMAX_DELAY); + if (err == ESP_OK) { + err = spi_device_polling_end(this->handle_, portMAX_DELAY); + } + if (err != ESP_OK) { + ESP_LOGE(TAG, "Transmit failed - err %X", err); + return; + } + // if more data is to be sent, skip the command and address phases. + desc.command_bits = 0; + desc.address_bits = 0; + } while (length != 0); + } + void transfer(uint8_t *ptr, size_t length) override { this->transfer(ptr, ptr, length); } uint8_t transfer(uint8_t data) override { @@ -142,13 +196,27 @@ class SPIDelegateHw : public SPIDelegate { class SPIBusHw : public SPIBus { public: - SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel) : SPIBus(clk, sdo, sdi), channel_(channel) { + SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel, std::vector data_pins) + : SPIBus(clk, sdo, sdi), channel_(channel) { spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = Utility::get_pin_no(sdo); - buscfg.miso_io_num = Utility::get_pin_no(sdi); buscfg.sclk_io_num = Utility::get_pin_no(clk); - buscfg.quadwp_io_num = -1; - buscfg.quadhd_io_num = -1; + buscfg.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_SCLK; + if (data_pins.empty()) { + buscfg.mosi_io_num = Utility::get_pin_no(sdo); + buscfg.miso_io_num = Utility::get_pin_no(sdi); + buscfg.quadwp_io_num = -1; + buscfg.quadhd_io_num = -1; + } else { + buscfg.data0_io_num = data_pins[0]; + buscfg.data1_io_num = data_pins[1]; + buscfg.data2_io_num = data_pins[2]; + buscfg.data3_io_num = data_pins[3]; + buscfg.data4_io_num = -1; + buscfg.data5_io_num = -1; + buscfg.data6_io_num = -1; + buscfg.data7_io_num = -1; + buscfg.flags |= SPICOMMON_BUSFLAG_QUAD; + } buscfg.max_transfer_sz = MAX_TRANSFER_SIZE; auto err = spi_bus_initialize(channel, &buscfg, SPI_DMA_CH_AUTO); if (err != ESP_OK) @@ -166,8 +234,9 @@ class SPIBusHw : public SPIBus { bool is_hw() override { return true; } }; -SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) { - return new SPIBusHw(clk, sdo, sdi, interface); +SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, + const std::vector &data_pins) { + return new SPIBusHw(clk, sdo, sdi, interface, data_pins); } #endif diff --git a/esphome/components/spi_device/spi_device.cpp b/esphome/components/spi_device/spi_device.cpp index 4e0b72ae60..1f579cb802 100644 --- a/esphome/components/spi_device/spi_device.cpp +++ b/esphome/components/spi_device/spi_device.cpp @@ -1,6 +1,7 @@ #include "spi_device.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" +#include namespace esphome { namespace spi_device { @@ -18,9 +19,9 @@ void SPIDeviceComponent::dump_config() { LOG_PIN(" CS pin: ", this->cs_); ESP_LOGCONFIG(TAG, " Mode: %d", this->mode_); if (this->data_rate_ < 1000000) { - ESP_LOGCONFIG(TAG, " Data rate: %dkHz", this->data_rate_ / 1000); + ESP_LOGCONFIG(TAG, " Data rate: %" PRId32 "kHz", this->data_rate_ / 1000); } else { - ESP_LOGCONFIG(TAG, " Data rate: %dMHz", this->data_rate_ / 1000000); + ESP_LOGCONFIG(TAG, " Data rate: %" PRId32 "MHz", this->data_rate_ / 1000000); } } diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 749c3511c1..90b805a79f 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -236,6 +236,7 @@ void SSD1306::set_invert(bool invert) { // Inverse display mode (0xA6, 0xA7) this->command(SSD1306_COMMAND_NORMAL_DISPLAY | this->invert_); } +float SSD1306::get_contrast() { return this->contrast_; }; void SSD1306::set_contrast(float contrast) { // validation this->contrast_ = clamp(contrast, 0.0F, 1.0F); @@ -243,6 +244,7 @@ void SSD1306::set_contrast(float contrast) { this->command(SSD1306_COMMAND_SET_CONTRAST); this->command(int(SSD1306_MAX_CONTRAST * (this->contrast_))); } +float SSD1306::get_brightness() { return this->brightness_; }; void SSD1306::set_brightness(float brightness) { // validation if (!this->is_ssd1305_()) diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 2e09755863..14ec309ae0 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -36,7 +36,9 @@ class SSD1306 : public display::DisplayBuffer { void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } void set_external_vcc(bool external_vcc) { this->external_vcc_ = external_vcc; } void init_contrast(float contrast) { this->contrast_ = contrast; } + float get_contrast(); void set_contrast(float contrast); + float get_brightness(); void init_brightness(float brightness) { this->brightness_ = brightness; } void set_brightness(float brightness); void init_flip_x(bool flip_x) { this->flip_x_ = flip_x; } diff --git a/esphome/components/st7567_base/__init__.py b/esphome/components/st7567_base/__init__.py new file mode 100644 index 0000000000..7ce50fd99f --- /dev/null +++ b/esphome/components/st7567_base/__init__.py @@ -0,0 +1,55 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import display +from esphome.const import ( + CONF_LAMBDA, + CONF_RESET_PIN, + CONF_MIRROR_X, + CONF_MIRROR_Y, + CONF_TRANSFORM, + CONF_INVERT_COLORS, +) + +CODEOWNERS = ["@latonita"] + +st7567_base_ns = cg.esphome_ns.namespace("st7567_base") +ST7567 = st7567_base_ns.class_("ST7567", cg.PollingComponent, display.DisplayBuffer) +ST7567Model = st7567_base_ns.enum("ST7567Model") + +# todo in future: reuse following constants from const.py when they are released + + +ST7567_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( + { + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, + cv.Optional(CONF_TRANSFORM): cv.Schema( + { + cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, + } + ), + } +).extend(cv.polling_component_schema("1s")) + + +async def setup_st7567(var, config): + await display.register_display(var, config) + + if CONF_RESET_PIN in config: + reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) + cg.add(var.set_reset_pin(reset)) + + cg.add(var.init_invert_colors(config[CONF_INVERT_COLORS])) + + if CONF_TRANSFORM in config: + transform = config[CONF_TRANSFORM] + cg.add(var.init_mirror_x(transform[CONF_MIRROR_X])) + cg.add(var.init_mirror_y(transform[CONF_MIRROR_Y])) + + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/st7567_base/st7567_base.cpp b/esphome/components/st7567_base/st7567_base.cpp new file mode 100644 index 0000000000..b22a7d7fd5 --- /dev/null +++ b/esphome/components/st7567_base/st7567_base.cpp @@ -0,0 +1,152 @@ +#include "st7567_base.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace st7567_base { + +static const char *const TAG = "st7567"; + +void ST7567::setup() { + this->init_internal_(this->get_buffer_length_()); + this->display_init_(); +} + +void ST7567::display_init_() { + ESP_LOGD(TAG, "Initializing ST7567 display..."); + this->display_init_registers_(); + this->clear(); + this->write_display_data(); + this->command(ST7567_DISPLAY_ON); +} + +void ST7567::display_init_registers_() { + this->command(ST7567_BIAS_9); + this->command(this->mirror_x_ ? ST7567_SEG_REVERSE : ST7567_SEG_NORMAL); + this->command(this->mirror_y_ ? ST7567_COM_NORMAL : ST7567_COM_REMAP); + this->command(ST7567_POWER_CTL | 0x4); + this->command(ST7567_POWER_CTL | 0x6); + this->command(ST7567_POWER_CTL | 0x7); + + this->set_brightness(this->brightness_); + this->set_contrast(this->contrast_); + + this->command(ST7567_INVERT_OFF | this->invert_colors_); + + this->command(ST7567_BOOSTER_ON); + this->command(ST7567_REGULATOR_ON); + this->command(ST7567_POWER_ON); + + this->command(ST7567_SCAN_START_LINE); + this->command(ST7567_PIXELS_NORMAL | this->all_pixels_on_); +} + +void ST7567::display_sw_refresh_() { + ESP_LOGD(TAG, "Performing refresh sequence..."); + this->command(ST7567_SW_REFRESH); + this->display_init_registers_(); +} + +void ST7567::request_refresh() { + // as per datasheet: It is recommended to use the refresh sequence regularly in a specified interval. + this->refresh_requested_ = true; +} + +void ST7567::update() { + this->do_update_(); + if (this->refresh_requested_) { + this->refresh_requested_ = false; + this->display_sw_refresh_(); + } + this->write_display_data(); +} + +void ST7567::set_all_pixels_on(bool enable) { + this->all_pixels_on_ = enable; + this->command(ST7567_PIXELS_NORMAL | this->all_pixels_on_); +} + +void ST7567::set_invert_colors(bool invert_colors) { + this->invert_colors_ = invert_colors; + this->command(ST7567_INVERT_OFF | this->invert_colors_); +} + +void ST7567::set_contrast(uint8_t val) { + this->contrast_ = val & 0b111111; + // 0..63, 26 is normal + + // two byte command + // first byte 0x81 + // second byte 0-63 + + this->command(ST7567_SET_EV_CMD); + this->command(this->contrast_); +} + +void ST7567::set_brightness(uint8_t val) { + this->brightness_ = val & 0b111; + // 0..7, 5 normal + + //********Adjust display brightness******** + // 0x20-0x27 is the internal Rb/Ra resistance + // adjustment setting of V5 voltage RR=4.5V + + this->command(ST7567_RESISTOR_RATIO | this->brightness_); +} + +bool ST7567::is_on() { return this->is_on_; } + +void ST7567::turn_on() { + this->command(ST7567_DISPLAY_ON); + this->is_on_ = true; +} + +void ST7567::turn_off() { + this->command(ST7567_DISPLAY_OFF); + this->is_on_ = false; +} + +void ST7567::set_scroll(uint8_t line) { this->start_line_ = line % this->get_height_internal(); } + +int ST7567::get_width_internal() { return 128; } + +int ST7567::get_height_internal() { return 64; } + +// 128x64, but memory size 132x64, line starts from 0, but if mirrored then it starts from 131, not 127 +size_t ST7567::get_buffer_length_() { + return size_t(this->get_width_internal() + 4) * size_t(this->get_height_internal()) / 8u; +} + +void HOT ST7567::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) { + return; + } + + uint16_t pos = x + (y / 8) * this->get_width_internal(); + uint8_t subpos = y & 0x07; + if (color.is_on()) { + this->buffer_[pos] |= (1 << subpos); + } else { + this->buffer_[pos] &= ~(1 << subpos); + } +} + +void ST7567::fill(Color color) { memset(buffer_, color.is_on() ? 0xFF : 0x00, this->get_buffer_length_()); } + +void ST7567::init_reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->reset_pin_->digital_write(true); + delay(1); + // Trigger Reset + this->reset_pin_->digital_write(false); + delay(10); + // Wake up + this->reset_pin_->digital_write(true); + } +} + +const char *ST7567::model_str_() { return "ST7567 128x64"; } + +} // namespace st7567_base +} // namespace esphome diff --git a/esphome/components/st7567_base/st7567_base.h b/esphome/components/st7567_base/st7567_base.h new file mode 100644 index 0000000000..e3053673d1 --- /dev/null +++ b/esphome/components/st7567_base/st7567_base.h @@ -0,0 +1,100 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/display/display_buffer.h" + +namespace esphome { +namespace st7567_base { + +static const uint8_t ST7567_BOOSTER_ON = 0x2C; // internal power supply on +static const uint8_t ST7567_REGULATOR_ON = 0x2E; // internal power supply on +static const uint8_t ST7567_POWER_ON = 0x2F; // internal power supply on + +static const uint8_t ST7567_DISPLAY_ON = 0xAF; // Display ON. Normal Display Mode. +static const uint8_t ST7567_DISPLAY_OFF = 0xAE; // Display OFF. All SEGs/COMs output with VSS +static const uint8_t ST7567_SET_START_LINE = 0x40; +static const uint8_t ST7567_POWER_CTL = 0x28; +static const uint8_t ST7567_SEG_NORMAL = 0xA0; // +static const uint8_t ST7567_SEG_REVERSE = 0xA1; // mirror X axis (horizontal) +static const uint8_t ST7567_COM_NORMAL = 0xC0; // +static const uint8_t ST7567_COM_REMAP = 0xC8; // mirror Y axis (vertical) +static const uint8_t ST7567_PIXELS_NORMAL = 0xA4; // display ram content +static const uint8_t ST7567_PIXELS_ALL_ON = 0xA5; // all pixels on +static const uint8_t ST7567_INVERT_OFF = 0xA6; // normal pixels +static const uint8_t ST7567_INVERT_ON = 0xA7; // inverted pixels +static const uint8_t ST7567_SCAN_START_LINE = 0x40; // scrolling = 0x40 + (0..63) +static const uint8_t ST7567_COL_ADDR_H = 0x10; // x pos (0..95) 4 MSB +static const uint8_t ST7567_COL_ADDR_L = 0x00; // x pos (0..95) 4 LSB +static const uint8_t ST7567_PAGE_ADDR = 0xB0; // y pos, 8.5 rows (0..8) +static const uint8_t ST7567_BIAS_9 = 0xA2; +static const uint8_t ST7567_CONTRAST = 0x80; // 0x80 + (0..31) +static const uint8_t ST7567_SET_EV_CMD = 0x81; +static const uint8_t ST7567_SET_EV_PARAM = 0x00; +static const uint8_t ST7567_RESISTOR_RATIO = 0x20; +static const uint8_t ST7567_SW_REFRESH = 0xE2; + +class ST7567 : public display::DisplayBuffer { + public: + void setup() override; + + void update() override; + + void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } + void init_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; } + void init_mirror_y(bool mirror_y) { this->mirror_y_ = mirror_y; } + void init_invert_colors(bool invert_colors) { this->invert_colors_ = invert_colors; } + + void set_invert_colors(bool invert_colors); // inversion of screen colors + void set_contrast(uint8_t val); // 0..63, 27-30 normal + void set_brightness(uint8_t val); // 0..7, 5 normal + void set_all_pixels_on(bool enable); // turn on all pixels, this doesn't affect RAM + void set_scroll(uint8_t line); // set display start line: for screen scrolling w/o affecting RAM + + bool is_on(); + void turn_on(); + void turn_off(); + + void request_refresh(); // from datasheet: It is recommended to use the refresh sequence regularly in a specified + // interval. + + float get_setup_priority() const override { return setup_priority::PROCESSOR; } + void fill(Color color) override; + + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; } + + protected: + virtual void command(uint8_t value) = 0; + virtual void write_display_data() = 0; + + void init_reset_(); + void display_init_(); + void display_init_registers_(); + void display_sw_refresh_(); + + void draw_absolute_pixel_internal(int x, int y, Color color) override; + + int get_height_internal() override; + int get_width_internal() override; + size_t get_buffer_length_(); + + int get_offset_x_() { return mirror_x_ ? 4 : 0; }; + + const char *model_str_(); + + GPIOPin *reset_pin_{nullptr}; + bool is_on_{false}; + // float contrast_{1.0}; + // float brightness_{1.0}; + uint8_t contrast_{27}; + uint8_t brightness_{5}; + bool mirror_x_{true}; + bool mirror_y_{true}; + bool invert_colors_{false}; + bool all_pixels_on_{false}; + uint8_t start_line_{0}; + bool refresh_requested_{false}; +}; + +} // namespace st7567_base +} // namespace esphome diff --git a/esphome/components/st7567_i2c/__init__.py b/esphome/components/st7567_i2c/__init__.py new file mode 100644 index 0000000000..dd06cfffea --- /dev/null +++ b/esphome/components/st7567_i2c/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@latonita"] diff --git a/esphome/components/st7567_i2c/display.py b/esphome/components/st7567_i2c/display.py new file mode 100644 index 0000000000..fa92d652d7 --- /dev/null +++ b/esphome/components/st7567_i2c/display.py @@ -0,0 +1,29 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import st7567_base, i2c +from esphome.const import CONF_ID, CONF_LAMBDA, CONF_PAGES + +CODEOWNERS = ["@latonita"] + +AUTO_LOAD = ["st7567_base"] +DEPENDENCIES = ["i2c"] + +st7567_i2c = cg.esphome_ns.namespace("st7567_i2c") +I2CST7567 = st7567_i2c.class_("I2CST7567", st7567_base.ST7567, i2c.I2CDevice) + +CONFIG_SCHEMA = cv.All( + st7567_base.ST7567_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(I2CST7567), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x3F)), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await st7567_base.setup_st7567(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/st7567_i2c/st7567_i2c.cpp b/esphome/components/st7567_i2c/st7567_i2c.cpp new file mode 100644 index 0000000000..05173d1be5 --- /dev/null +++ b/esphome/components/st7567_i2c/st7567_i2c.cpp @@ -0,0 +1,60 @@ +#include "st7567_i2c.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace st7567_i2c { + +static const char *const TAG = "st7567_i2c"; + +void I2CST7567::setup() { + ESP_LOGCONFIG(TAG, "Setting up I2C ST7567 display..."); + this->init_reset_(); + + auto err = this->write(nullptr, 0); + if (err != i2c::ERROR_OK) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + ST7567::setup(); +} + +void I2CST7567::dump_config() { + LOG_DISPLAY("", "I2CST7567", this); + LOG_I2C_DEVICE(this); + ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + ESP_LOGCONFIG(TAG, " Mirror X: %s", YESNO(this->mirror_x_)); + ESP_LOGCONFIG(TAG, " Mirror Y: %s", YESNO(this->mirror_y_)); + ESP_LOGCONFIG(TAG, " Invert Colors: %s", YESNO(this->invert_colors_)); + LOG_UPDATE_INTERVAL(this); + + if (this->error_code_ == COMMUNICATION_FAILED) { + ESP_LOGE(TAG, "Communication with I2C ST7567 failed!"); + } +} + +void I2CST7567::command(uint8_t value) { this->write_byte(0x00, value); } + +void HOT I2CST7567::write_display_data() { + // ST7567A has built-in RAM with 132x65 bit capacity which stores the display data. + // but only first 128 pixels from each line are shown on screen + // if screen got flipped horizontally then it shows last 128 pixels, + // so we need to write x coordinate starting from column 4, not column 0 + this->command(esphome::st7567_base::ST7567_SET_START_LINE + this->start_line_); + for (uint8_t y = 0; y < (uint8_t) this->get_height_internal() / 8; y++) { + this->command(esphome::st7567_base::ST7567_PAGE_ADDR + y); // Set Page + this->command(esphome::st7567_base::ST7567_COL_ADDR_H); // Set MSB Column address + this->command(esphome::st7567_base::ST7567_COL_ADDR_L + this->get_offset_x_()); // Set LSB Column address + + static const size_t BLOCK_SIZE = 64; + for (uint8_t x = 0; x < (uint8_t) this->get_width_internal(); x += BLOCK_SIZE) { + this->write_register(esphome::st7567_base::ST7567_SET_START_LINE, &buffer_[y * this->get_width_internal() + x], + this->get_width_internal() - x > BLOCK_SIZE ? BLOCK_SIZE : this->get_width_internal() - x, + true); + } + } +} + +} // namespace st7567_i2c +} // namespace esphome diff --git a/esphome/components/st7567_i2c/st7567_i2c.h b/esphome/components/st7567_i2c/st7567_i2c.h new file mode 100644 index 0000000000..f760a54945 --- /dev/null +++ b/esphome/components/st7567_i2c/st7567_i2c.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/st7567_base/st7567_base.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace st7567_i2c { + +class I2CST7567 : public st7567_base::ST7567, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + + protected: + void command(uint8_t value) override; + void write_display_data() override; + + enum ErrorCode { NONE = 0, COMMUNICATION_FAILED } error_code_{NONE}; +}; + +} // namespace st7567_i2c +} // namespace esphome diff --git a/esphome/components/st7567_spi/__init__.py b/esphome/components/st7567_spi/__init__.py new file mode 100644 index 0000000000..dd06cfffea --- /dev/null +++ b/esphome/components/st7567_spi/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@latonita"] diff --git a/esphome/components/st7567_spi/display.py b/esphome/components/st7567_spi/display.py new file mode 100644 index 0000000000..aabe02a2d8 --- /dev/null +++ b/esphome/components/st7567_spi/display.py @@ -0,0 +1,34 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import spi, st7567_base +from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES + +CODEOWNERS = ["@latonita"] + +AUTO_LOAD = ["st7567_base"] +DEPENDENCIES = ["spi"] + +st7567_spi = cg.esphome_ns.namespace("st7567_spi") +SPIST7567 = st7567_spi.class_("SPIST7567", st7567_base.ST7567, spi.SPIDevice) + +CONFIG_SCHEMA = cv.All( + st7567_base.ST7567_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SPIST7567), + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema()), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await st7567_base.setup_st7567(var, config) + await spi.register_spi_device(var, config) + + dc = await cg.gpio_pin_expression(config[CONF_DC_PIN]) + cg.add(var.set_dc_pin(dc)) diff --git a/esphome/components/st7567_spi/st7567_spi.cpp b/esphome/components/st7567_spi/st7567_spi.cpp new file mode 100644 index 0000000000..25698d0dd0 --- /dev/null +++ b/esphome/components/st7567_spi/st7567_spi.cpp @@ -0,0 +1,66 @@ +#include "st7567_spi.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace st7567_spi { + +static const char *const TAG = "st7567_spi"; + +void SPIST7567::setup() { + ESP_LOGCONFIG(TAG, "Setting up SPI ST7567 display..."); + this->spi_setup(); + this->dc_pin_->setup(); + if (this->cs_) + this->cs_->setup(); + + this->init_reset_(); + ST7567::setup(); +} + +void SPIST7567::dump_config() { + LOG_DISPLAY("", "SPI ST7567", this); + ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); + LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + ESP_LOGCONFIG(TAG, " Mirror X: %s", YESNO(this->mirror_x_)); + ESP_LOGCONFIG(TAG, " Mirror Y: %s", YESNO(this->mirror_y_)); + ESP_LOGCONFIG(TAG, " Invert Colors: %s", YESNO(this->invert_colors_)); + LOG_UPDATE_INTERVAL(this); +} + +void SPIST7567::command(uint8_t value) { + if (this->cs_) + this->cs_->digital_write(true); + this->dc_pin_->digital_write(false); + delay(1); + this->enable(); + if (this->cs_) + this->cs_->digital_write(false); + this->write_byte(value); + if (this->cs_) + this->cs_->digital_write(true); + this->disable(); +} + +void HOT SPIST7567::write_display_data() { + // ST7567A has built-in RAM with 132x65 bit capacity which stores the display data. + // but only first 128 pixels from each line are shown on screen + // if screen got flipped horizontally then it shows last 128 pixels, + // so we need to write x coordinate starting from column 4, not column 0 + this->command(esphome::st7567_base::ST7567_SET_START_LINE + this->start_line_); + for (uint8_t y = 0; y < (uint8_t) this->get_height_internal() / 8; y++) { + this->dc_pin_->digital_write(false); + this->command(esphome::st7567_base::ST7567_PAGE_ADDR + y); // Set Page + this->command(esphome::st7567_base::ST7567_COL_ADDR_H); // Set MSB Column address + this->command(esphome::st7567_base::ST7567_COL_ADDR_L + this->get_offset_x_()); // Set LSB Column address + this->dc_pin_->digital_write(true); + + this->enable(); + this->write_array(&this->buffer_[y * this->get_width_internal()], this->get_width_internal()); + this->disable(); + } +} + +} // namespace st7567_spi +} // namespace esphome diff --git a/esphome/components/st7567_spi/st7567_spi.h b/esphome/components/st7567_spi/st7567_spi.h new file mode 100644 index 0000000000..e8d1ebe0cc --- /dev/null +++ b/esphome/components/st7567_spi/st7567_spi.h @@ -0,0 +1,29 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/st7567_base/st7567_base.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace st7567_spi { + +class SPIST7567 : public st7567_base::ST7567, + public spi::SPIDevice { + public: + void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } + + void setup() override; + + void dump_config() override; + + protected: + void command(uint8_t value) override; + + void write_display_data() override; + + GPIOPin *dc_pin_; +}; + +} // namespace st7567_spi +} // namespace esphome diff --git a/esphome/components/st7701s/__init__.py b/esphome/components/st7701s/__init__.py new file mode 100644 index 0000000000..c58ce8a01e --- /dev/null +++ b/esphome/components/st7701s/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@clydebarrow"] diff --git a/esphome/components/st7701s/display.py b/esphome/components/st7701s/display.py new file mode 100644 index 0000000000..e33eeb89ae --- /dev/null +++ b/esphome/components/st7701s/display.py @@ -0,0 +1,253 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import ( + spi, + display, +) +from esphome.const import ( + CONF_DC_PIN, + CONF_RESET_PIN, + CONF_DATA_PINS, + CONF_ID, + CONF_DIMENSIONS, + CONF_WIDTH, + CONF_HEIGHT, + CONF_LAMBDA, + CONF_MIRROR_X, + CONF_MIRROR_Y, + CONF_COLOR_ORDER, + CONF_TRANSFORM, + CONF_OFFSET_HEIGHT, + CONF_OFFSET_WIDTH, + CONF_INVERT_COLORS, + CONF_RED, + CONF_GREEN, + CONF_BLUE, + CONF_NUMBER, + CONF_IGNORE_STRAPPING_WARNING, +) + +from esphome.components.esp32 import ( + only_on_variant, + const, +) +from esphome.components.rpi_dpi_rgb.display import ( + CONF_PCLK_FREQUENCY, + CONF_PCLK_INVERTED, +) +from .init_sequences import ( + ST7701S_INITS, + cmd, +) + +CONF_INIT_SEQUENCE = "init_sequence" +CONF_DE_PIN = "de_pin" +CONF_PCLK_PIN = "pclk_pin" +CONF_HSYNC_PIN = "hsync_pin" +CONF_VSYNC_PIN = "vsync_pin" + +CONF_HSYNC_PULSE_WIDTH = "hsync_pulse_width" +CONF_HSYNC_BACK_PORCH = "hsync_back_porch" +CONF_HSYNC_FRONT_PORCH = "hsync_front_porch" +CONF_VSYNC_PULSE_WIDTH = "vsync_pulse_width" +CONF_VSYNC_BACK_PORCH = "vsync_back_porch" +CONF_VSYNC_FRONT_PORCH = "vsync_front_porch" + +DEPENDENCIES = ["spi", "esp32"] + +st7701s_ns = cg.esphome_ns.namespace("st7701s") +ST7701S = st7701s_ns.class_("ST7701S", display.Display, cg.Component, spi.SPIDevice) +ColorOrder = display.display_ns.enum("ColorMode") + +COLOR_ORDERS = { + "RGB": ColorOrder.COLOR_ORDER_RGB, + "BGR": ColorOrder.COLOR_ORDER_BGR, +} +DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema + + +def data_pin_validate(value): + """ + It is safe to use strapping pins as RGB output data bits, as they are outputs only, + and not initialised until after boot. + """ + if not isinstance(value, dict): + try: + return DATA_PIN_SCHEMA( + {CONF_NUMBER: value, CONF_IGNORE_STRAPPING_WARNING: True} + ) + except cv.Invalid: + pass + return DATA_PIN_SCHEMA(value) + + +def data_pin_set(length): + return cv.All( + [data_pin_validate], + cv.Length(min=length, max=length, msg=f"Exactly {length} data pins required"), + ) + + +def map_sequence(value): + """ + An initialisation sequence can be selected from one of the pre-defined sequences in init_sequences.py, + or can be a literal array of data bytes. + The format is a repeated sequence of [CMD, LEN, ] where is LEN bytes. + """ + if not isinstance(value, list): + value = cv.int_(value) + value = cv.one_of(*ST7701S_INITS)(value) + return ST7701S_INITS[value] + # value = cv.ensure_list(cv.uint8_t)(value) + data_length = len(value) + if data_length == 0: + raise cv.Invalid("Empty sequence") + value = cmd(*value) + return value + + +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ST7701S), + cv.Required(CONF_DIMENSIONS): cv.Any( + cv.dimensions, + cv.Schema( + { + cv.Required(CONF_WIDTH): cv.int_, + cv.Required(CONF_HEIGHT): cv.int_, + cv.Optional(CONF_OFFSET_HEIGHT, default=0): cv.int_, + cv.Optional(CONF_OFFSET_WIDTH, default=0): cv.int_, + } + ), + ), + cv.Optional(CONF_TRANSFORM): cv.Schema( + { + cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, + } + ), + cv.Required(CONF_DATA_PINS): cv.Any( + data_pin_set(16), + cv.Schema( + { + cv.Required(CONF_RED): data_pin_set(5), + cv.Required(CONF_GREEN): data_pin_set(6), + cv.Required(CONF_BLUE): data_pin_set(5), + } + ), + ), + cv.Optional(CONF_INIT_SEQUENCE, default=1): cv.ensure_list( + map_sequence + ), + cv.Optional(CONF_COLOR_ORDER): cv.one_of( + *COLOR_ORDERS.keys(), upper=True + ), + cv.Optional(CONF_PCLK_FREQUENCY, default="16MHz"): cv.All( + cv.frequency, cv.Range(min=4e6, max=30e6) + ), + cv.Optional(CONF_PCLK_INVERTED, default=True): cv.boolean, + cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, + cv.Required(CONF_DE_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_PCLK_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_HSYNC_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_DC_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_HSYNC_PULSE_WIDTH, default=10): cv.int_, + cv.Optional(CONF_HSYNC_BACK_PORCH, default=10): cv.int_, + cv.Optional(CONF_HSYNC_FRONT_PORCH, default=20): cv.int_, + cv.Optional(CONF_VSYNC_PULSE_WIDTH, default=10): cv.int_, + cv.Optional(CONF_VSYNC_BACK_PORCH, default=10): cv.int_, + cv.Optional(CONF_VSYNC_FRONT_PORCH, default=10): cv.int_, + } + ).extend(spi.spi_device_schema(cs_pin_required=False, default_data_rate=1e6)) + ), + only_on_variant(supported=[const.VARIANT_ESP32S3]), + cv.only_with_esp_idf, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await display.register_display(var, config) + await spi.register_spi_device(var, config) + + sequence = [] + for seq in config[CONF_INIT_SEQUENCE]: + sequence.extend(seq) + cg.add(var.set_init_sequence(sequence)) + cg.add(var.set_color_mode(COLOR_ORDERS[config[CONF_COLOR_ORDER]])) + cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS])) + cg.add(var.set_hsync_pulse_width(config[CONF_HSYNC_PULSE_WIDTH])) + cg.add(var.set_hsync_back_porch(config[CONF_HSYNC_BACK_PORCH])) + cg.add(var.set_hsync_front_porch(config[CONF_HSYNC_FRONT_PORCH])) + cg.add(var.set_vsync_pulse_width(config[CONF_VSYNC_PULSE_WIDTH])) + cg.add(var.set_vsync_back_porch(config[CONF_VSYNC_BACK_PORCH])) + cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH])) + cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED])) + cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY])) + index = 0 + dpins = [] + if CONF_RED in config[CONF_DATA_PINS]: + red_pins = config[CONF_DATA_PINS][CONF_RED] + green_pins = config[CONF_DATA_PINS][CONF_GREEN] + blue_pins = config[CONF_DATA_PINS][CONF_BLUE] + if config[CONF_COLOR_ORDER] == "BGR": + dpins.extend(red_pins) + dpins.extend(green_pins) + dpins.extend(blue_pins) + else: + dpins.extend(blue_pins) + dpins.extend(green_pins) + dpins.extend(red_pins) + # swap bytes to match big-endian format + dpins = dpins[8:16] + dpins[0:8] + else: + dpins = config[CONF_DATA_PINS] + for pin in dpins: + data_pin = await cg.gpio_pin_expression(pin) + cg.add(var.add_data_pin(data_pin, index)) + index += 1 + + if dc_pin := config.get(CONF_DC_PIN): + dc = await cg.gpio_pin_expression(dc_pin) + cg.add(var.set_dc_pin(dc)) + + if reset_pin := config.get(CONF_RESET_PIN): + reset = await cg.gpio_pin_expression(reset_pin) + cg.add(var.set_reset_pin(reset)) + + if transform := config.get(CONF_TRANSFORM): + cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) + cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y])) + + if CONF_DIMENSIONS in config: + dimensions = config[CONF_DIMENSIONS] + if isinstance(dimensions, dict): + cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT])) + cg.add( + var.set_offsets( + dimensions[CONF_OFFSET_WIDTH], dimensions[CONF_OFFSET_HEIGHT] + ) + ) + else: + (width, height) = dimensions + cg.add(var.set_dimensions(width, height)) + + if lamb := config.get(CONF_LAMBDA): + lambda_ = await cg.process_lambda( + lamb, [(display.DisplayRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) + + pin = await cg.gpio_pin_expression(config[CONF_DE_PIN]) + cg.add(var.set_de_pin(pin)) + pin = await cg.gpio_pin_expression(config[CONF_PCLK_PIN]) + cg.add(var.set_pclk_pin(pin)) + pin = await cg.gpio_pin_expression(config[CONF_HSYNC_PIN]) + cg.add(var.set_hsync_pin(pin)) + pin = await cg.gpio_pin_expression(config[CONF_VSYNC_PIN]) + cg.add(var.set_vsync_pin(pin)) diff --git a/esphome/components/st7701s/init_sequences.py b/esphome/components/st7701s/init_sequences.py new file mode 100644 index 0000000000..4786731c78 --- /dev/null +++ b/esphome/components/st7701s/init_sequences.py @@ -0,0 +1,363 @@ +# These are initialisation sequences for ST7701S displays. The contents are somewhat arcane. + + +def cmd(c, *args): + """ + Create a command sequence + :param c: The command (8 bit) + :param args: zero or more arguments (8 bit values) + :return: a list with the command, the argument count and the arguments + """ + return [c, len(args)] + list(args) + + +ST7701S_1_INIT = ( + cmd(0x01) + + cmd(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10) + + cmd(0xC0, 0x3B, 0x00) + + cmd(0xC1, 0x0D, 0x02) + + cmd(0xC2, 0x31, 0x05) + + cmd(0xCD, 0x08) + + cmd( + 0xB0, + 0x00, + 0x11, + 0x18, + 0x0E, + 0x11, + 0x06, + 0x07, + 0x08, + 0x07, + 0x22, + 0x04, + 0x12, + 0x0F, + 0xAA, + 0x31, + 0x18, + ) + + cmd( + 0xB1, + 0x00, + 0x11, + 0x19, + 0x0E, + 0x12, + 0x07, + 0x08, + 0x08, + 0x08, + 0x22, + 0x04, + 0x11, + 0x11, + 0xA9, + 0x32, + 0x18, + ) + + cmd(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11) + + cmd(0xB0, 0x60) + + cmd(0xB1, 0x32) + + cmd(0xB2, 0x07) + + cmd(0xB3, 0x80) + + cmd(0xB5, 0x49) + + cmd(0xB7, 0x85) + + cmd(0xB8, 0x21) + + cmd(0xC1, 0x78) + + cmd(0xC2, 0x78) + + cmd(0xE0, 0x00, 0x1B, 0x02) + + cmd(0xE1, 0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44) + + cmd(0xE2, 0x11, 0x11, 0x44, 0x44, 0xED, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00) + + cmd(0xE3, 0x00, 0x00, 0x11, 0x11) + + cmd(0xE4, 0x44, 0x44) + + cmd( + 0xE5, + 0x0A, + 0xE9, + 0xD8, + 0xA0, + 0x0C, + 0xEB, + 0xD8, + 0xA0, + 0x0E, + 0xED, + 0xD8, + 0xA0, + 0x10, + 0xEF, + 0xD8, + 0xA0, + ) + + cmd(0xE6, 0x00, 0x00, 0x11, 0x11) + + cmd(0xE7, 0x44, 0x44) + + cmd( + 0xE8, + 0x09, + 0xE8, + 0xD8, + 0xA0, + 0x0B, + 0xEA, + 0xD8, + 0xA0, + 0x0D, + 0xEC, + 0xD8, + 0xA0, + 0x0F, + 0xEE, + 0xD8, + 0xA0, + ) + + cmd(0xEB, 0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40) + + cmd(0xEC, 0x3C, 0x00) + + cmd( + 0xED, + 0xAB, + 0x89, + 0x76, + 0x54, + 0x02, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x20, + 0x45, + 0x67, + 0x98, + 0xBA, + ) + + cmd(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13) + + cmd(0xE5, 0xE4) + + cmd(0x3A, 0x60) +) + +# This is untested +ST7701S_7_INIT = ( + cmd( + 0xFF, + 0x77, + 0x01, + 0x00, + 0x00, + 0x10, + ) + + cmd(0xC0, 0x3B, 0x00) + + cmd(0xC1, 0x0B, 0x02) + + cmd(0xC2, 0x07, 0x02) + + cmd(0xCC, 0x10) + + cmd(0xCD, 0x08) + + cmd( + 0xB0, + 0x00, + 0x11, + 0x16, + 0x0E, + 0x11, + 0x06, + 0x05, + 0x09, + 0x08, + 0x21, + 0x06, + 0x13, + 0x10, + 0x29, + 0x31, + 0x18, + ) + + cmd( + 0xB1, + 0x00, + 0x11, + 0x16, + 0x0E, + 0x11, + 0x07, + 0x05, + 0x09, + 0x09, + 0x21, + 0x05, + 0x13, + 0x11, + 0x2A, + 0x31, + 0x18, + ) + + cmd( + 0xFF, + 0x77, + 0x01, + 0x00, + 0x00, + 0x11, + ) + + cmd(0xB0, 0x6D) + + cmd(0xB1, 0x37) + + cmd(0xB2, 0x81) + + cmd(0xB3, 0x80) + + cmd(0xB5, 0x43) + + cmd(0xB7, 0x85) + + cmd(0xB8, 0x20) + + cmd(0xC1, 0x78) + + cmd(0xC2, 0x78) + + cmd(0xD0, 0x88) + + cmd( + 0xE0, + 3, + 0x00, + 0x00, + 0x02, + ) + + cmd( + 0xE1, + 0x03, + 0xA0, + 0x00, + 0x00, + 0x04, + 0xA0, + 0x00, + 0x00, + 0x00, + 0x20, + 0x20, + ) + + cmd( + 0xE2, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ) + + cmd( + 0xE3, + 0x00, + 0x00, + 0x11, + 0x00, + ) + + cmd(0xE4, 0x22, 0x00) + + cmd( + 0xE5, + 0x05, + 0xEC, + 0xA0, + 0xA0, + 0x07, + 0xEE, + 0xA0, + 0xA0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ) + + cmd( + 0xE6, + 0x00, + 0x00, + 0x11, + 0x00, + ) + + cmd(0xE7, 0x22, 0x00) + + cmd( + 0xE8, + 0x06, + 0xED, + 0xA0, + 0xA0, + 0x08, + 0xEF, + 0xA0, + 0xA0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ) + + cmd( + 0xEB, + 0x00, + 0x00, + 0x40, + 0x40, + 0x00, + 0x00, + 0x00, + ) + + cmd( + 0xED, + 0xFF, + 0xFF, + 0xFF, + 0xBA, + 0x0A, + 0xBF, + 0x45, + 0xFF, + 0xFF, + 0x54, + 0xFB, + 0xA0, + 0xAB, + 0xFF, + 0xFF, + 0xFF, + ) + + cmd( + 0xEF, + 0x10, + 0x0D, + 0x04, + 0x08, + 0x3F, + 0x1F, + ) + + cmd( + 0xFF, + 0x77, + 0x01, + 0x00, + 0x00, + 0x13, + ) + + cmd(0xEF, 0x08) + + cmd( + 0xFF, + 0x77, + 0x01, + 0x00, + 0x00, + 0x00, + ) + + cmd(0x3A, 0x66) +) + +ST7701S_INITS = { + 1: ST7701S_1_INIT, + # 7: ST7701S_7_INIT, +} diff --git a/esphome/components/st7701s/st7701s.cpp b/esphome/components/st7701s/st7701s.cpp new file mode 100644 index 0000000000..43d8653709 --- /dev/null +++ b/esphome/components/st7701s/st7701s.cpp @@ -0,0 +1,180 @@ +#ifdef USE_ESP32_VARIANT_ESP32S3 +#include "st7701s.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace st7701s { + +void ST7701S::setup() { + esph_log_config(TAG, "Setting up ST7701S"); + this->spi_setup(); + esp_lcd_rgb_panel_config_t config{}; + config.flags.fb_in_psram = 1; + config.timings.h_res = this->width_; + config.timings.v_res = this->height_; + config.timings.hsync_pulse_width = this->hsync_pulse_width_; + config.timings.hsync_back_porch = this->hsync_back_porch_; + config.timings.hsync_front_porch = this->hsync_front_porch_; + config.timings.vsync_pulse_width = this->vsync_pulse_width_; + config.timings.vsync_back_porch = this->vsync_back_porch_; + config.timings.vsync_front_porch = this->vsync_front_porch_; + config.timings.flags.pclk_active_neg = this->pclk_inverted_; + config.timings.pclk_hz = this->pclk_frequency_; + config.clk_src = LCD_CLK_SRC_PLL160M; + config.sram_trans_align = 64; + config.psram_trans_align = 64; + size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]); + for (size_t i = 0; i != data_pin_count; i++) { + config.data_gpio_nums[i] = this->data_pins_[i]->get_pin(); + } + config.data_width = data_pin_count; + config.disp_gpio_num = -1; + config.hsync_gpio_num = this->hsync_pin_->get_pin(); + config.vsync_gpio_num = this->vsync_pin_->get_pin(); + config.de_gpio_num = this->de_pin_->get_pin(); + config.pclk_gpio_num = this->pclk_pin_->get_pin(); + esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_); + if (err != ESP_OK) { + esph_log_e(TAG, "lcd_new_rgb_panel failed: %s", esp_err_to_name(err)); + } + ESP_ERROR_CHECK(esp_lcd_panel_reset(this->handle_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(this->handle_)); + this->write_init_sequence_(); + esph_log_config(TAG, "ST7701S setup complete"); +} + +void ST7701S::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { + if (w <= 0 || h <= 0) + return; + // if color mapping is required, pass the buck. + // note that endianness is not considered here - it is assumed to match! + if (bitness != display::COLOR_BITNESS_565) { + return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, + x_pad); + } + x_start += this->offset_x_; + y_start += this->offset_y_; + esp_err_t err; + // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. + if (x_offset == 0 && x_pad == 0 && y_offset == 0) { + // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother + err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr); + } else { + // draw line by line + auto stride = x_offset + w + x_pad; + for (int y = 0; y != h; y++) { + err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y + y_start, x_start + w, y + y_start + 1, + ptr + ((y + y_offset) * stride + x_offset) * 2); + if (err != ESP_OK) + break; + } + } + if (err != ESP_OK) + esph_log_e(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err)); +} + +void ST7701S::draw_pixel_at(int x, int y, Color color) { + if (!this->get_clipping().inside(x, y)) + return; // NOLINT + + switch (this->rotation_) { + case display::DISPLAY_ROTATION_0_DEGREES: + break; + case display::DISPLAY_ROTATION_90_DEGREES: + std::swap(x, y); + x = this->width_ - x - 1; + break; + case display::DISPLAY_ROTATION_180_DEGREES: + x = this->width_ - x - 1; + y = this->height_ - y - 1; + break; + case display::DISPLAY_ROTATION_270_DEGREES: + std::swap(x, y); + y = this->height_ - y - 1; + break; + } + auto pixel = convert_big_endian(display::ColorUtil::color_to_565(color)); + + this->draw_pixels_at(x, y, 1, 1, (const uint8_t *) &pixel, display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, true, + 0, 0, 0); + App.feed_wdt(); +} + +void ST7701S::write_command_(uint8_t value) { + this->enable(); + if (this->dc_pin_ == nullptr) { + this->write(value, 9); + } else { + this->dc_pin_->digital_write(false); + this->write_byte(value); + this->dc_pin_->digital_write(true); + } + this->disable(); +} + +void ST7701S::write_data_(uint8_t value) { + this->enable(); + if (this->dc_pin_ == nullptr) { + this->write(value | 0x100, 9); + } else { + this->dc_pin_->digital_write(true); + this->write_byte(value); + } + this->disable(); +} + +/** + * this relies upon the init sequence being well-formed, which is guaranteed by the Python init code. + */ + +void ST7701S::write_sequence_(uint8_t cmd, size_t len, const uint8_t *bytes) { + this->write_command_(cmd); + while (len-- != 0) + this->write_data_(*bytes++); +} + +void ST7701S::write_init_sequence_() { + for (size_t i = 0; i != this->init_sequence_.size();) { + uint8_t cmd = this->init_sequence_[i++]; + size_t len = this->init_sequence_[i++]; + this->write_sequence_(cmd, len, &this->init_sequence_[i]); + i += len; + esph_log_v(TAG, "Command %X, %d bytes", cmd, len); + if (cmd == SW_RESET_CMD) + delay(6); + } + // st7701 does not appear to support axis swapping + this->write_sequence_(CMD2_BKSEL, sizeof(CMD2_BK0), CMD2_BK0); + this->write_command_(SDIR_CMD); // this is in the BK0 command set + this->write_data_(this->mirror_x_ ? 0x04 : 0x00); + uint8_t val = this->color_mode_ == display::COLOR_ORDER_BGR ? 0x08 : 0x00; + if (this->mirror_y_) + val |= 0x10; + this->write_command_(MADCTL_CMD); + this->write_data_(val); + esph_log_d(TAG, "write MADCTL %X", val); + this->write_command_(this->invert_colors_ ? INVERT_ON : INVERT_OFF); + this->set_timeout(120, [this] { + this->write_command_(SLEEP_OUT); + this->write_command_(DISPLAY_ON); + }); +} + +void ST7701S::dump_config() { + ESP_LOGCONFIG("", "ST7701S RGB LCD"); + ESP_LOGCONFIG(TAG, " Height: %u", this->height_); + ESP_LOGCONFIG(TAG, " Width: %u", this->width_); + LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" DE Pin: ", this->de_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]); + for (size_t i = 0; i != data_pin_count; i++) + ESP_LOGCONFIG(TAG, " Data pin %d: %s", i, (this->data_pins_[i])->dump_summary().c_str()); + ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); +} + +} // namespace st7701s +} // namespace esphome +#endif // USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/st7701s/st7701s.h b/esphome/components/st7701s/st7701s.h new file mode 100644 index 0000000000..2328bca965 --- /dev/null +++ b/esphome/components/st7701s/st7701s.h @@ -0,0 +1,115 @@ +// +// Created by Clyde Stubbs on 29/10/2023. +// +#pragma once + +// only applicable on ESP32-S3 +#ifdef USE_ESP32_VARIANT_ESP32S3 +#include "esphome/core/component.h" +#include "esphome/components/spi/spi.h" +#include "esphome/components/display/display.h" +#include "esp_lcd_panel_ops.h" + +#include "esp_lcd_panel_rgb.h" + +namespace esphome { +namespace st7701s { + +constexpr static const char *const TAG = "display.st7701s"; +const uint8_t SW_RESET_CMD = 0x01; +const uint8_t SLEEP_OUT = 0x11; +const uint8_t SDIR_CMD = 0xC7; +const uint8_t MADCTL_CMD = 0x36; +const uint8_t INVERT_OFF = 0x20; +const uint8_t INVERT_ON = 0x21; +const uint8_t DISPLAY_ON = 0x29; +const uint8_t CMD2_BKSEL = 0xFF; +const uint8_t CMD2_BK0[5] = {0x77, 0x01, 0x00, 0x00, 0x10}; + +class ST7701S : public display::Display, + public spi::SPIDevice { + public: + void update() override { this->do_update_(); } + void setup() override; + void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; + + display::ColorOrder get_color_mode() { return this->color_mode_; } + void set_color_mode(display::ColorOrder color_mode) { this->color_mode_ = color_mode; } + void set_invert_colors(bool invert_colors) { this->invert_colors_ = invert_colors; } + + void add_data_pin(InternalGPIOPin *data_pin, size_t index) { this->data_pins_[index] = data_pin; }; + void set_de_pin(InternalGPIOPin *de_pin) { this->de_pin_ = de_pin; } + void set_pclk_pin(InternalGPIOPin *pclk_pin) { this->pclk_pin_ = pclk_pin; } + void set_vsync_pin(InternalGPIOPin *vsync_pin) { this->vsync_pin_ = vsync_pin; } + void set_hsync_pin(InternalGPIOPin *hsync_pin) { this->hsync_pin_ = hsync_pin; } + void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; } + void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } + void set_width(uint16_t width) { this->width_ = width; } + void set_pclk_frequency(uint32_t pclk_frequency) { this->pclk_frequency_ = pclk_frequency; } + void set_pclk_inverted(bool inverted) { this->pclk_inverted_ = inverted; } + void set_dimensions(uint16_t width, uint16_t height) { + this->width_ = width; + this->height_ = height; + } + int get_width() override { return this->width_; } + int get_height() override { return this->height_; } + void set_hsync_back_porch(uint16_t hsync_back_porch) { this->hsync_back_porch_ = hsync_back_porch; } + void set_hsync_front_porch(uint16_t hsync_front_porch) { this->hsync_front_porch_ = hsync_front_porch; } + void set_hsync_pulse_width(uint16_t hsync_pulse_width) { this->hsync_pulse_width_ = hsync_pulse_width; } + void set_vsync_pulse_width(uint16_t vsync_pulse_width) { this->vsync_pulse_width_ = vsync_pulse_width; } + void set_vsync_back_porch(uint16_t vsync_back_porch) { this->vsync_back_porch_ = vsync_back_porch; } + void set_vsync_front_porch(uint16_t vsync_front_porch) { this->vsync_front_porch_ = vsync_front_porch; } + void set_init_sequence(const std::vector &init_sequence) { this->init_sequence_ = init_sequence; } + void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; } + void set_mirror_y(bool mirror_y) { this->mirror_y_ = mirror_y; } + void set_offsets(int16_t offset_x, int16_t offset_y) { + this->offset_x_ = offset_x; + this->offset_y_ = offset_y; + } + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + int get_width_internal() override { return this->width_; } + int get_height_internal() override { return this->height_; } + void dump_config() override; + void draw_pixel_at(int x, int y, Color color) override; + + // this will be horribly slow. + protected: + void write_command_(uint8_t value); + void write_data_(uint8_t value); + void write_sequence_(uint8_t cmd, size_t len, const uint8_t *bytes); + void write_init_sequence_(); + + InternalGPIOPin *de_pin_{nullptr}; + InternalGPIOPin *pclk_pin_{nullptr}; + InternalGPIOPin *hsync_pin_{nullptr}; + InternalGPIOPin *vsync_pin_{nullptr}; + GPIOPin *reset_pin_{nullptr}; + GPIOPin *dc_pin_{nullptr}; + InternalGPIOPin *data_pins_[16] = {}; + uint16_t hsync_pulse_width_ = 10; + uint16_t hsync_back_porch_ = 10; + uint16_t hsync_front_porch_ = 20; + uint16_t vsync_pulse_width_ = 10; + uint16_t vsync_back_porch_ = 10; + uint16_t vsync_front_porch_ = 10; + std::vector init_sequence_; + uint32_t pclk_frequency_ = 16 * 1000 * 1000; + bool pclk_inverted_{true}; + + bool invert_colors_{}; + display::ColorOrder color_mode_{display::COLOR_ORDER_BGR}; + size_t width_{}; + size_t height_{}; + int16_t offset_x_{0}; + int16_t offset_y_{0}; + bool mirror_x_{}; + bool mirror_y_{}; + + esp_lcd_panel_handle_t handle_{}; +}; + +} // namespace st7701s +} // namespace esphome +#endif diff --git a/esphome/components/st7735/display.py b/esphome/components/st7735/display.py index 4ff5cafaf8..d5bb2fa3d6 100644 --- a/esphome/components/st7735/display.py +++ b/esphome/components/st7735/display.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_MODEL, CONF_RESET_PIN, CONF_PAGES, + CONF_INVERT_COLORS, ) from . import st7735_ns @@ -23,7 +24,6 @@ CONF_ROW_START = "row_start" CONF_COL_START = "col_start" CONF_EIGHT_BIT_COLOR = "eight_bit_color" CONF_USE_BGR = "use_bgr" -CONF_INVERT_COLORS = "invert_colors" SPIST7735 = st7735_ns.class_( "ST7735", cg.PollingComponent, display.DisplayBuffer, spi.SPIDevice diff --git a/esphome/components/st7789v/display.py b/esphome/components/st7789v/display.py index 41970afd26..04dce2cf6c 100644 --- a/esphome/components/st7789v/display.py +++ b/esphome/components/st7789v/display.py @@ -14,12 +14,12 @@ from esphome.const import ( CONF_POWER_SUPPLY, CONF_ROTATION, CONF_CS_PIN, + CONF_OFFSET_HEIGHT, + CONF_OFFSET_WIDTH, ) from . import st7789v_ns CONF_EIGHTBITCOLOR = "eightbitcolor" -CONF_OFFSET_HEIGHT = "offset_height" -CONF_OFFSET_WIDTH = "offset_width" CODEOWNERS = ["@kbx81"] @@ -97,6 +97,19 @@ MODELS = { CONF_BACKLIGHT_PIN: "GPIO15", } ), + "WAVESHARE_1.47IN_172X320": model_spec( + presets={ + CONF_HEIGHT: 320, + CONF_WIDTH: 172, + CONF_OFFSET_HEIGHT: 34, + CONF_OFFSET_WIDTH: 0, + CONF_ROTATION: 90, + CONF_CS_PIN: "GPIO21", + CONF_DC_PIN: "GPIO22", + CONF_RESET_PIN: "GPIO23", + CONF_BACKLIGHT_PIN: "GPIO4", + } + ), "CUSTOM": model_spec(), } diff --git a/esphome/components/sun_gtil2/__init__.py b/esphome/components/sun_gtil2/__init__.py new file mode 100644 index 0000000000..f4d46fade7 --- /dev/null +++ b/esphome/components/sun_gtil2/__init__.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID + +CODEOWNERS = ["@Mat931"] +MULTI_CONF = True +DEPENDENCIES = ["uart"] + +CONF_SUN_GTIL2_ID = "sun_gtil2_id" + +sun_gtil2_ns = cg.esphome_ns.namespace("sun_gtil2") + +SunGTIL2Component = sun_gtil2_ns.class_("SunGTIL2", cg.Component, uart.UARTDevice) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(SunGTIL2Component), + } +).extend(uart.UART_DEVICE_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) diff --git a/esphome/components/sun_gtil2/sensor.py b/esphome/components/sun_gtil2/sensor.py new file mode 100644 index 0000000000..6d1be9c740 --- /dev/null +++ b/esphome/components/sun_gtil2/sensor.py @@ -0,0 +1,87 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + ICON_FLASH, + UNIT_VOLT, + ICON_THERMOMETER, + UNIT_WATT, + UNIT_CELSIUS, + CONF_TEMPERATURE, +) +from . import SunGTIL2Component, CONF_SUN_GTIL2_ID + +CONF_AC_VOLTAGE = "ac_voltage" +CONF_DC_VOLTAGE = "dc_voltage" +CONF_AC_POWER = "ac_power" +CONF_DC_POWER = "dc_power" +CONF_LIMITER_POWER = "limiter_power" + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_SUN_GTIL2_ID): cv.use_id(SunGTIL2Component), + cv.Optional(CONF_AC_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + ), + cv.Optional(CONF_DC_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + ), + cv.Optional(CONF_AC_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + icon=ICON_FLASH, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + ), + cv.Optional(CONF_DC_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + icon=ICON_FLASH, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + ), + cv.Optional(CONF_LIMITER_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + icon=ICON_FLASH, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + ), + } + ).extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_SUN_GTIL2_ID]) + if ac_voltage_config := config.get(CONF_AC_VOLTAGE): + sens = await sensor.new_sensor(ac_voltage_config) + cg.add(hub.set_ac_voltage(sens)) + if dc_voltage_config := config.get(CONF_DC_VOLTAGE): + sens = await sensor.new_sensor(dc_voltage_config) + cg.add(hub.set_dc_voltage(sens)) + if ac_power_config := config.get(CONF_AC_POWER): + sens = await sensor.new_sensor(ac_power_config) + cg.add(hub.set_ac_power(sens)) + if dc_power_config := config.get(CONF_DC_POWER): + sens = await sensor.new_sensor(dc_power_config) + cg.add(hub.set_dc_power(sens)) + if limiter_power_config := config.get(CONF_LIMITER_POWER): + sens = await sensor.new_sensor(limiter_power_config) + cg.add(hub.set_limiter_power(sens)) + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(hub.set_temperature(sens)) diff --git a/esphome/components/sun_gtil2/sun_gtil2.cpp b/esphome/components/sun_gtil2/sun_gtil2.cpp new file mode 100644 index 0000000000..1653f937dd --- /dev/null +++ b/esphome/components/sun_gtil2/sun_gtil2.cpp @@ -0,0 +1,135 @@ +#include "sun_gtil2.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace sun_gtil2 { + +static const char *const TAG = "sun_gtil2"; + +static const double NTC_A = 0.0011591051055979914; +static const double NTC_B = 0.00022878183547845582; +static const double NTC_C = 1.0396291358342124e-07; +static const float PULLUP_RESISTANCE = 10000.0f; +static const uint16_t ADC_MAX = 1023; // ADC of the inverter controller, not the ESP + +struct SunGTIL2Message { + uint16_t sync; + uint8_t ac_waveform[277]; + uint8_t frequency; + uint16_t ac_voltage; + uint16_t ac_power; + uint16_t dc_voltage; + uint8_t state; + uint8_t unknown1; + uint8_t unknown2; + uint8_t unknown3; + uint8_t limiter_mode; + uint8_t unknown4; + uint16_t temperature; + uint32_t limiter_power; + uint16_t dc_power; + char serial_number[10]; + uint8_t unknown5; + uint8_t end[39]; +} __attribute__((packed)); + +static const uint16_t MESSAGE_SIZE = sizeof(SunGTIL2Message); + +static_assert(MESSAGE_SIZE == 350, "Expected the message size to be 350 bytes"); + +void SunGTIL2::setup() { this->rx_message_.reserve(MESSAGE_SIZE); } + +void SunGTIL2::loop() { + while (this->available()) { + uint8_t c; + this->read_byte(&c); + this->handle_char_(c); + } +} + +std::string SunGTIL2::state_to_string_(uint8_t state) { + switch (state) { + case 0x02: + return "Starting voltage too low"; + case 0x07: + return "Working"; + default: + return str_sprintf("Unknown (0x%02x)", state); + } +} + +float SunGTIL2::calculate_temperature_(uint16_t adc_value) { + if (adc_value >= ADC_MAX || adc_value == 0) { + return NAN; + } + + float ntc_resistance = PULLUP_RESISTANCE / ((static_cast(ADC_MAX) / adc_value) - 1.0f); + double lr = log(double(ntc_resistance)); + double v = NTC_A + NTC_B * lr + NTC_C * lr * lr * lr; + return float(1.0 / v - 273.15); +} + +void SunGTIL2::handle_char_(uint8_t c) { + if (this->rx_message_.size() > 1 || c == 0x07) { + this->rx_message_.push_back(c); + } else if (!this->rx_message_.empty()) { + this->rx_message_.clear(); + } + if (this->rx_message_.size() < MESSAGE_SIZE) { + return; + } + + SunGTIL2Message msg; + memcpy(&msg, this->rx_message_.data(), MESSAGE_SIZE); + this->rx_message_.clear(); + + if (!((msg.end[0] == 0) && (msg.end[38] == 0x08))) + return; + + ESP_LOGVV(TAG, "Frequency raw value: %02x", msg.frequency); + ESP_LOGVV(TAG, "Unknown values: %02x %02x %02x %02x %02x", msg.unknown1, msg.unknown2, msg.unknown3, msg.unknown4, + msg.unknown5); + +#ifdef USE_SENSOR + if (this->ac_voltage_ != nullptr) + this->ac_voltage_->publish_state(__builtin_bswap16(msg.ac_voltage) / 10.0f); + if (this->dc_voltage_ != nullptr) + this->dc_voltage_->publish_state(__builtin_bswap16(msg.dc_voltage) / 8.0f); + if (this->ac_power_ != nullptr) + this->ac_power_->publish_state(__builtin_bswap16(msg.ac_power) / 10.0f); + if (this->dc_power_ != nullptr) + this->dc_power_->publish_state(__builtin_bswap16(msg.dc_power) / 10.0f); + if (this->limiter_power_ != nullptr) + this->limiter_power_->publish_state(static_cast(__builtin_bswap32(msg.limiter_power)) / 10.0f); + if (this->temperature_ != nullptr) + this->temperature_->publish_state(calculate_temperature_(__builtin_bswap16(msg.temperature))); +#endif +#ifdef USE_TEXT_SENSOR + if (this->state_ != nullptr) { + this->state_->publish_state(this->state_to_string_(msg.state)); + } + if (this->serial_number_ != nullptr) { + std::string serial_number; + serial_number.assign(msg.serial_number, 10); + this->serial_number_->publish_state(serial_number); + } +#endif +} + +void SunGTIL2::dump_config() { +#ifdef USE_SENSOR + LOG_SENSOR("", "AC Voltage", this->ac_voltage_); + LOG_SENSOR("", "DC Voltage", this->dc_voltage_); + LOG_SENSOR("", "AC Power", this->ac_power_); + LOG_SENSOR("", "DC Power", this->dc_power_); + LOG_SENSOR("", "Limiter Power", this->limiter_power_); + LOG_SENSOR("", "Temperature", this->temperature_); +#endif +#ifdef USE_TEXT_SENSOR + LOG_TEXT_SENSOR("", "State", this->state_); + LOG_TEXT_SENSOR("", "Serial Number", this->serial_number_); +#endif +} + +} // namespace sun_gtil2 +} // namespace esphome diff --git a/esphome/components/sun_gtil2/sun_gtil2.h b/esphome/components/sun_gtil2/sun_gtil2.h new file mode 100644 index 0000000000..0c29ae695d --- /dev/null +++ b/esphome/components/sun_gtil2/sun_gtil2.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" + +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace sun_gtil2 { + +class SunGTIL2 : public Component, public uart::UARTDevice { + public: + float get_setup_priority() const override { return setup_priority::LATE; } + void setup() override; + void loop() override; + void dump_config() override; + +#ifdef USE_SENSOR + void set_ac_voltage(sensor::Sensor *sensor) { ac_voltage_ = sensor; } + void set_dc_voltage(sensor::Sensor *sensor) { dc_voltage_ = sensor; } + void set_ac_power(sensor::Sensor *sensor) { ac_power_ = sensor; } + void set_dc_power(sensor::Sensor *sensor) { dc_power_ = sensor; } + void set_limiter_power(sensor::Sensor *sensor) { limiter_power_ = sensor; } + void set_temperature(sensor::Sensor *sensor) { temperature_ = sensor; } +#endif +#ifdef USE_TEXT_SENSOR + void set_state(text_sensor::TextSensor *text_sensor) { state_ = text_sensor; } + void set_serial_number(text_sensor::TextSensor *text_sensor) { serial_number_ = text_sensor; } +#endif + + protected: + std::string state_to_string_(uint8_t state); +#ifdef USE_SENSOR + sensor::Sensor *ac_voltage_{nullptr}; + sensor::Sensor *dc_voltage_{nullptr}; + sensor::Sensor *ac_power_{nullptr}; + sensor::Sensor *dc_power_{nullptr}; + sensor::Sensor *limiter_power_{nullptr}; + sensor::Sensor *temperature_{nullptr}; +#endif +#ifdef USE_TEXT_SENSOR + text_sensor::TextSensor *state_{nullptr}; + text_sensor::TextSensor *serial_number_{nullptr}; +#endif + + float calculate_temperature_(uint16_t adc_value); + void handle_char_(uint8_t c); + std::vector rx_message_; +}; + +} // namespace sun_gtil2 +} // namespace esphome diff --git a/esphome/components/sun_gtil2/text_sensor.py b/esphome/components/sun_gtil2/text_sensor.py new file mode 100644 index 0000000000..d9d3e3ca66 --- /dev/null +++ b/esphome/components/sun_gtil2/text_sensor.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import CONF_STATE +from . import SunGTIL2Component, CONF_SUN_GTIL2_ID + +CONF_SERIAL_NUMBER = "serial_number" + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_SUN_GTIL2_ID): cv.use_id(SunGTIL2Component), + cv.Optional(CONF_STATE): text_sensor.text_sensor_schema( + text_sensor.TextSensor + ), + cv.Optional(CONF_SERIAL_NUMBER): text_sensor.text_sensor_schema( + text_sensor.TextSensor + ), + } + ).extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_SUN_GTIL2_ID]) + if state_config := config.get(CONF_STATE): + sens = await text_sensor.new_text_sensor(state_config) + cg.add(hub.set_state(sens)) + if serial_number_config := config.get(CONF_SERIAL_NUMBER): + sens = await text_sensor.new_text_sensor(serial_number_config) + cg.add(hub.set_serial_number(sens)) diff --git a/esphome/components/template/alarm_control_panel/__init__.py b/esphome/components/template/alarm_control_panel/__init__.py index 27b7e92b4f..3555f2fafd 100644 --- a/esphome/components/template/alarm_control_panel/__init__.py +++ b/esphome/components/template/alarm_control_panel/__init__.py @@ -12,11 +12,13 @@ from esphome.const import ( ) from .. import template_ns -CODEOWNERS = ["@grahambrown11"] +CODEOWNERS = ["@grahambrown11", "@hwstar"] CONF_CODES = "codes" CONF_BYPASS_ARMED_HOME = "bypass_armed_home" CONF_BYPASS_ARMED_NIGHT = "bypass_armed_night" +CONF_CHIME = "chime" +CONF_TRIGGER_MODE = "trigger_mode" CONF_REQUIRES_CODE_TO_ARM = "requires_code_to_arm" CONF_ARMING_HOME_TIME = "arming_home_time" CONF_ARMING_NIGHT_TIME = "arming_night_time" @@ -24,16 +26,20 @@ CONF_ARMING_AWAY_TIME = "arming_away_time" CONF_PENDING_TIME = "pending_time" CONF_TRIGGER_TIME = "trigger_time" + FLAG_NORMAL = "normal" FLAG_BYPASS_ARMED_HOME = "bypass_armed_home" FLAG_BYPASS_ARMED_NIGHT = "bypass_armed_night" +FLAG_CHIME = "chime" BinarySensorFlags = { FLAG_NORMAL: 1 << 0, FLAG_BYPASS_ARMED_HOME: 1 << 1, FLAG_BYPASS_ARMED_NIGHT: 1 << 2, + FLAG_CHIME: 1 << 3, } + TemplateAlarmControlPanel = template_ns.class_( "TemplateAlarmControlPanel", alarm_control_panel.AlarmControlPanel, cg.Component ) @@ -46,6 +52,14 @@ RESTORE_MODES = { "RESTORE_DEFAULT_DISARMED": TemplateAlarmControlPanelRestoreMode.ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED, } +AlarmSensorType = template_ns.enum("AlarmSensorType") + +ALARM_SENSOR_TYPES = { + "DELAYED": AlarmSensorType.ALARM_SENSOR_TYPE_DELAYED, + "INSTANT": AlarmSensorType.ALARM_SENSOR_TYPE_INSTANT, + "DELAYED_FOLLOWER": AlarmSensorType.ALARM_SENSOR_TYPE_DELAYED_FOLLOWER, +} + def validate_config(config): if config.get(CONF_REQUIRES_CODE_TO_ARM, False) and not config.get(CONF_CODES, []): @@ -60,6 +74,10 @@ TEMPLATE_ALARM_CONTROL_PANEL_BINARY_SENSOR_SCHEMA = cv.maybe_simple_value( cv.Required(CONF_INPUT): cv.use_id(binary_sensor.BinarySensor), cv.Optional(CONF_BYPASS_ARMED_HOME, default=False): cv.boolean, cv.Optional(CONF_BYPASS_ARMED_NIGHT, default=False): cv.boolean, + cv.Optional(CONF_CHIME, default=False): cv.boolean, + cv.Optional(CONF_TRIGGER_MODE, default="DELAYED"): cv.enum( + ALARM_SENSOR_TYPES, upper=True, space="_" + ), }, key=CONF_INPUT, ) @@ -123,6 +141,7 @@ async def to_code(config): for sensor in config.get(CONF_BINARY_SENSORS, []): bs = await cg.get_variable(sensor[CONF_INPUT]) + flags = BinarySensorFlags[FLAG_NORMAL] if sensor[CONF_BYPASS_ARMED_HOME]: flags |= BinarySensorFlags[FLAG_BYPASS_ARMED_HOME] @@ -130,7 +149,9 @@ async def to_code(config): if sensor[CONF_BYPASS_ARMED_NIGHT]: flags |= BinarySensorFlags[FLAG_BYPASS_ARMED_NIGHT] supports_arm_night = True - cg.add(var.add_sensor(bs, flags)) + if sensor[CONF_CHIME]: + flags |= BinarySensorFlags[FLAG_CHIME] + cg.add(var.add_sensor(bs, flags, sensor[CONF_TRIGGER_MODE])) cg.add(var.set_supports_arm_home(supports_arm_home)) cg.add(var.set_supports_arm_night(supports_arm_night)) diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp index b39b587811..99843417fa 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp @@ -1,3 +1,4 @@ + #include "template_alarm_control_panel.h" #include #include "esphome/components/alarm_control_panel/alarm_control_panel.h" @@ -15,8 +16,14 @@ static const char *const TAG = "template.alarm_control_panel"; TemplateAlarmControlPanel::TemplateAlarmControlPanel(){}; #ifdef USE_BINARY_SENSOR -void TemplateAlarmControlPanel::add_sensor(binary_sensor::BinarySensor *sensor, uint16_t flags) { - this->sensor_map_[sensor] = flags; +void TemplateAlarmControlPanel::add_sensor(binary_sensor::BinarySensor *sensor, uint16_t flags, AlarmSensorType type) { + // Save the flags and type. Assign a store index for the per sensor data type. + SensorDataStore sd; + sd.last_chime_state = false; + this->sensor_map_[sensor].flags = flags; + this->sensor_map_[sensor].type = type; + this->sensor_data_.push_back(sd); + this->sensor_map_[sensor].store_index = this->next_store_index_++; }; #endif @@ -35,13 +42,27 @@ void TemplateAlarmControlPanel::dump_config() { ESP_LOGCONFIG(TAG, " Trigger Time: %" PRIu32 "s", (this->trigger_time_ / 1000)); ESP_LOGCONFIG(TAG, " Supported Features: %" PRIu32, this->get_supported_features()); #ifdef USE_BINARY_SENSOR - for (auto sensor_pair : this->sensor_map_) { - ESP_LOGCONFIG(TAG, " Binary Sesnsor:"); - ESP_LOGCONFIG(TAG, " Name: %s", sensor_pair.first->get_name().c_str()); + for (auto sensor_info : this->sensor_map_) { + ESP_LOGCONFIG(TAG, " Binary Sensor:"); + ESP_LOGCONFIG(TAG, " Name: %s", sensor_info.first->get_name().c_str()); ESP_LOGCONFIG(TAG, " Armed home bypass: %s", - TRUEFALSE(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)); + TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)); ESP_LOGCONFIG(TAG, " Armed night bypass: %s", - TRUEFALSE(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)); + TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)); + ESP_LOGCONFIG(TAG, " Chime mode: %s", TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_CHIME)); + const char *sensor_type; + switch (sensor_info.second.type) { + case ALARM_SENSOR_TYPE_INSTANT: + sensor_type = "instant"; + break; + case ALARM_SENSOR_TYPE_DELAYED_FOLLOWER: + sensor_type = "delayed_follower"; + break; + case ALARM_SENSOR_TYPE_DELAYED: + default: + sensor_type = "delayed"; + } + ESP_LOGCONFIG(TAG, " Sensor type: %s", sensor_type); } #endif } @@ -92,31 +113,80 @@ void TemplateAlarmControlPanel::loop() { (millis() - this->last_update_) > this->trigger_time_) { future_state = this->desired_state_; } - bool trigger = false; + + bool delayed_sensor_not_ready = false; + bool instant_sensor_not_ready = false; + #ifdef USE_BINARY_SENSOR - if (this->is_state_armed(future_state)) { - // TODO might be better to register change for each sensor in setup... - for (auto sensor_pair : this->sensor_map_) { - if (sensor_pair.first->state) { - if (this->current_state_ == ACP_STATE_ARMED_HOME && - (sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)) { - continue; + // Test all of the sensors in the list regardless of the alarm panel state + for (auto sensor_info : this->sensor_map_) { + // Check for chime zones + if ((sensor_info.second.flags & BINARY_SENSOR_MODE_CHIME)) { + // Look for the transition from closed to open + if ((!this->sensor_data_[sensor_info.second.store_index].last_chime_state) && (sensor_info.first->state)) { + // Must be disarmed to chime + if (this->current_state_ == ACP_STATE_DISARMED) { + this->chime_callback_.call(); } - if (this->current_state_ == ACP_STATE_ARMED_NIGHT && - (sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)) { - continue; + } + // Record the sensor state change + this->sensor_data_[sensor_info.second.store_index].last_chime_state = sensor_info.first->state; + } + // Check for triggered sensors + if (sensor_info.first->state) { // Sensor triggered? + // Skip if bypass armed home + if (this->current_state_ == ACP_STATE_ARMED_HOME && + (sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)) { + continue; + } + // Skip if bypass armed night + if (this->current_state_ == ACP_STATE_ARMED_NIGHT && + (sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)) { + continue; + } + + // If sensor type is of type instant + if (sensor_info.second.type == ALARM_SENSOR_TYPE_INSTANT) { + instant_sensor_not_ready = true; + break; + } + // If sensor type is of type interior follower + if (sensor_info.second.type == ALARM_SENSOR_TYPE_DELAYED_FOLLOWER) { + // Look to see if we are in the pending state + if (this->current_state_ == ACP_STATE_PENDING) { + delayed_sensor_not_ready = true; + } else { + instant_sensor_not_ready = true; } - trigger = true; + } + // If sensor type is of type delayed + if (sensor_info.second.type == ALARM_SENSOR_TYPE_DELAYED) { + delayed_sensor_not_ready = true; break; } } } + // Update all sensors not ready flag + this->sensors_ready_ = ((!instant_sensor_not_ready) && (!delayed_sensor_not_ready)); + + // Call the ready state change callback if there was a change + if (this->sensors_ready_ != this->sensors_ready_last_) { + this->ready_callback_.call(); + this->sensors_ready_last_ = this->sensors_ready_; + } + #endif - if (trigger) { - if (this->pending_time_ > 0 && this->current_state_ != ACP_STATE_TRIGGERED) { - this->publish_state(ACP_STATE_PENDING); - } else { + if (this->is_state_armed(future_state) && (!this->sensors_ready_)) { + // Instant sensors + if (instant_sensor_not_ready) { this->publish_state(ACP_STATE_TRIGGERED); + } else if (delayed_sensor_not_ready) { + // Delayed sensors + if ((this->pending_time_ > 0) && (this->current_state_ != ACP_STATE_TRIGGERED)) { + this->publish_state(ACP_STATE_PENDING); + } else { + this->publish_state(ACP_STATE_TRIGGERED); + } } } else if (future_state != this->current_state_) { this->publish_state(future_state); diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h index 9582ed157c..9ae69a0422 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h @@ -21,7 +21,15 @@ enum BinarySensorFlags : uint16_t { BINARY_SENSOR_MODE_NORMAL = 1 << 0, BINARY_SENSOR_MODE_BYPASS_ARMED_HOME = 1 << 1, BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT = 1 << 2, + BINARY_SENSOR_MODE_CHIME = 1 << 3, }; + +enum AlarmSensorType : uint16_t { + ALARM_SENSOR_TYPE_DELAYED = 0, + ALARM_SENSOR_TYPE_INSTANT, + ALARM_SENSOR_TYPE_DELAYED_FOLLOWER +}; + #endif enum TemplateAlarmControlPanelRestoreMode { @@ -29,6 +37,16 @@ enum TemplateAlarmControlPanelRestoreMode { ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED, }; +struct SensorDataStore { + bool last_chime_state; +}; + +struct SensorInfo { + uint16_t flags; + AlarmSensorType type; + uint8_t store_index; +}; + class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, public Component { public: TemplateAlarmControlPanel(); @@ -38,6 +56,7 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, uint32_t get_supported_features() const override; bool get_requires_code() const override; bool get_requires_code_to_arm() const override { return this->requires_code_to_arm_; } + bool get_all_sensors_ready() { return this->sensors_ready_; }; void set_restore_mode(TemplateAlarmControlPanelRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } #ifdef USE_BINARY_SENSOR @@ -46,7 +65,8 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, * @param sensor The BinarySensor instance. * @param ignore_when_home if this should be ignored when armed_home mode */ - void add_sensor(binary_sensor::BinarySensor *sensor, uint16_t flags = 0); + void add_sensor(binary_sensor::BinarySensor *sensor, uint16_t flags = 0, + AlarmSensorType type = ALARM_SENSOR_TYPE_DELAYED); #endif /** add a code @@ -98,8 +118,9 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, protected: void control(const alarm_control_panel::AlarmControlPanelCall &call) override; #ifdef USE_BINARY_SENSOR - // the map of binary sensors that the alarm_panel monitors with their modes - std::map sensor_map_; + // This maps a binary sensor to its type and attribute bits + std::map sensor_map_; + #endif TemplateAlarmControlPanelRestoreMode restore_mode_{}; @@ -115,10 +136,15 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, uint32_t trigger_time_; // a list of codes std::vector codes_; + // Per sensor data store + std::vector sensor_data_; // requires a code to arm bool requires_code_to_arm_ = false; bool supports_arm_home_ = false; bool supports_arm_night_ = false; + bool sensors_ready_ = false; + bool sensors_ready_last_ = false; + uint8_t next_store_index_ = 0; // check if the code is valid bool is_code_valid_(optional code); diff --git a/esphome/components/template/datetime/__init__.py b/esphome/components/template/datetime/__init__.py new file mode 100644 index 0000000000..16f341301e --- /dev/null +++ b/esphome/components/template/datetime/__init__.py @@ -0,0 +1,103 @@ +from esphome import automation +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import datetime +from esphome.const import ( + CONF_INITIAL_VALUE, + CONF_LAMBDA, + CONF_OPTIMISTIC, + CONF_RESTORE_VALUE, + CONF_SET_ACTION, + CONF_DAY, + CONF_MONTH, + CONF_YEAR, +) + +from esphome.core import coroutine_with_priority +from .. import template_ns + +CODEOWNERS = ["@rfdarter"] + + +TemplateDate = template_ns.class_( + "TemplateDate", datetime.DateEntity, cg.PollingComponent +) + + +def validate(config): + config = config.copy() + if CONF_LAMBDA in config: + if config[CONF_OPTIMISTIC]: + raise cv.Invalid("optimistic cannot be used with lambda") + if CONF_INITIAL_VALUE in config: + raise cv.Invalid("initial_value cannot be used with lambda") + if CONF_RESTORE_VALUE in config: + raise cv.Invalid("restore_value cannot be used with lambda") + else: + if CONF_RESTORE_VALUE not in config: + config[CONF_RESTORE_VALUE] = False + + if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: + raise cv.Invalid( + "Either optimistic mode must be enabled, or set_action must be set, to handle the date and time being set." + ) + return config + + +_BASE_SCHEMA = cv.Schema( + { + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_RESTORE_VALUE): cv.boolean, + } +).extend(cv.polling_component_schema("60s")) + +CONFIG_SCHEMA = cv.All( + cv.typed_schema( + { + "DATE": datetime.date_schema(TemplateDate) + .extend(_BASE_SCHEMA) + .extend( + { + cv.Optional(CONF_INITIAL_VALUE): cv.date_time(allowed_time=False), + } + ), + }, + upper=True, + ), + validate, +) + + +@coroutine_with_priority(-100.0) +async def to_code(config): + var = await datetime.new_datetime(config) + + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.ESPTime) + ) + cg.add(var.set_template(template_)) + + else: + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) + + if initial_value := config.get(CONF_INITIAL_VALUE): + date_struct = cg.StructInitializer( + cg.ESPTime, + ("day_of_month", initial_value[CONF_DAY]), + ("month", initial_value[CONF_MONTH]), + ("year", initial_value[CONF_YEAR]), + ) + cg.add(var.set_initial_value(date_struct)) + + if CONF_SET_ACTION in config: + await automation.build_automation( + var.get_set_trigger(), + [(cg.ESPTime, "x")], + config[CONF_SET_ACTION], + ) + + await cg.register_component(var, config) diff --git a/esphome/components/template/datetime/template_date.cpp b/esphome/components/template/datetime/template_date.cpp new file mode 100644 index 0000000000..01e15e532e --- /dev/null +++ b/esphome/components/template/datetime/template_date.cpp @@ -0,0 +1,111 @@ +#include "template_date.h" + +#ifdef USE_DATETIME_DATE + +#include "esphome/core/log.h" + +namespace esphome { +namespace template_ { + +static const char *const TAG = "template.date"; + +void TemplateDate::setup() { + if (this->f_.has_value()) + return; + + ESPTime state{}; + + if (!this->restore_value_) { + state = this->initial_value_; + } else { + datetime::DateEntityRestoreState temp; + this->pref_ = + global_preferences->make_preference(194434030U ^ this->get_object_id_hash()); + if (this->pref_.load(&temp)) { + temp.apply(this); + return; + } else { + // set to inital value if loading from pref failed + state = this->initial_value_; + } + } + + this->year_ = state.year; + this->month_ = state.month; + this->day_ = state.day_of_month; + this->publish_state(); +} + +void TemplateDate::update() { + if (!this->f_.has_value()) + return; + + auto val = (*this->f_)(); + if (!val.has_value()) + return; + + this->year_ = val->year; + this->month_ = val->month; + this->day_ = val->day_of_month; + this->publish_state(); +} + +void TemplateDate::control(const datetime::DateCall &call) { + bool has_year = call.get_year().has_value(); + bool has_month = call.get_month().has_value(); + bool has_day = call.get_day().has_value(); + + ESPTime value = {}; + if (has_year) + value.year = *call.get_year(); + + if (has_month) + value.month = *call.get_month(); + + if (has_day) + value.day_of_month = *call.get_day(); + + this->set_trigger_->trigger(value); + + if (this->optimistic_) { + if (has_year) + this->year_ = *call.get_year(); + if (has_month) + this->month_ = *call.get_month(); + if (has_day) + this->day_ = *call.get_day(); + this->publish_state(); + } + + if (this->restore_value_) { + datetime::DateEntityRestoreState temp = {}; + if (has_year) { + temp.year = *call.get_year(); + } else { + temp.year = this->year_; + } + if (has_month) { + temp.month = *call.get_month(); + } else { + temp.month = this->month_; + } + if (has_day) { + temp.day = *call.get_day(); + } else { + temp.day = this->day_; + } + + this->pref_.save(&temp); + } +} + +void TemplateDate::dump_config() { + LOG_DATETIME_DATE("", "Template Date", this); + ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); + LOG_UPDATE_INTERVAL(this); +} + +} // namespace template_ +} // namespace esphome + +#endif // USE_DATETIME_DATE diff --git a/esphome/components/template/datetime/template_date.h b/esphome/components/template/datetime/template_date.h new file mode 100644 index 0000000000..185c7ed49d --- /dev/null +++ b/esphome/components/template/datetime/template_date.h @@ -0,0 +1,46 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_DATETIME_DATE + +#include "esphome/components/datetime/date_entity.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/preferences.h" +#include "esphome/core/time.h" + +namespace esphome { +namespace template_ { + +class TemplateDate : public datetime::DateEntity, public PollingComponent { + public: + void set_template(std::function()> &&f) { this->f_ = f; } + + void setup() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + Trigger *get_set_trigger() const { return this->set_trigger_; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } + + void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } + void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } + + protected: + void control(const datetime::DateCall &call) override; + + bool optimistic_{false}; + ESPTime initial_value_{}; + bool restore_value_{false}; + Trigger *set_trigger_ = new Trigger(); + optional()>> f_; + + ESPPreferenceObject pref_; +}; + +} // namespace template_ +} // namespace esphome + +#endif // USE_DATETIME_DATE diff --git a/esphome/components/template/fan/__init__.py b/esphome/components/template/fan/__init__.py new file mode 100644 index 0000000000..348ebd281f --- /dev/null +++ b/esphome/components/template/fan/__init__.py @@ -0,0 +1,43 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import fan +from esphome.components.fan import validate_preset_modes +from esphome.const import ( + CONF_OUTPUT_ID, + CONF_PRESET_MODES, + CONF_SPEED_COUNT, +) + +from .. import template_ns + +CODEOWNERS = ["@ssieb"] + +TemplateFan = template_ns.class_("TemplateFan", cg.Component, fan.Fan) + +CONF_HAS_DIRECTION = "has_direction" +CONF_HAS_OSCILLATING = "has_oscillating" + +CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TemplateFan), + cv.Optional(CONF_HAS_DIRECTION, default=False): cv.boolean, + cv.Optional(CONF_HAS_OSCILLATING, default=False): cv.boolean, + cv.Optional(CONF_SPEED_COUNT): cv.int_range(min=1), + cv.Optional(CONF_PRESET_MODES): validate_preset_modes, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + await cg.register_component(var, config) + await fan.register_fan(var, config) + + cg.add(var.set_has_direction(config[CONF_HAS_DIRECTION])) + cg.add(var.set_has_oscillating(config[CONF_HAS_OSCILLATING])) + + if CONF_SPEED_COUNT in config: + cg.add(var.set_speed_count(config[CONF_SPEED_COUNT])) + + if CONF_PRESET_MODES in config: + cg.add(var.set_preset_modes(config[CONF_PRESET_MODES])) diff --git a/esphome/components/template/fan/template_fan.cpp b/esphome/components/template/fan/template_fan.cpp new file mode 100644 index 0000000000..5f4a2ae8f7 --- /dev/null +++ b/esphome/components/template/fan/template_fan.cpp @@ -0,0 +1,38 @@ +#include "template_fan.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace template_ { + +static const char *const TAG = "template.fan"; + +void TemplateFan::setup() { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(*this); + } + + // Construct traits + this->traits_ = + fan::FanTraits(this->has_oscillating_, this->speed_count_ > 0, this->has_direction_, this->speed_count_); + this->traits_.set_supported_preset_modes(this->preset_modes_); +} + +void TemplateFan::dump_config() { LOG_FAN("", "Template Fan", this); } + +void TemplateFan::control(const fan::FanCall &call) { + if (call.get_state().has_value()) + this->state = *call.get_state(); + if (call.get_speed().has_value() && (this->speed_count_ > 0)) + this->speed = *call.get_speed(); + if (call.get_oscillating().has_value() && this->has_oscillating_) + this->oscillating = *call.get_oscillating(); + if (call.get_direction().has_value() && this->has_direction_) + this->direction = *call.get_direction(); + this->preset_mode = call.get_preset_mode(); + + this->publish_state(); +} + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/template/fan/template_fan.h b/esphome/components/template/fan/template_fan.h new file mode 100644 index 0000000000..7f5305ca48 --- /dev/null +++ b/esphome/components/template/fan/template_fan.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "esphome/core/component.h" +#include "esphome/components/fan/fan.h" + +namespace esphome { +namespace template_ { + +class TemplateFan : public Component, public fan::Fan { + public: + TemplateFan() {} + void setup() override; + void dump_config() override; + void set_has_direction(bool has_direction) { this->has_direction_ = has_direction; } + void set_has_oscillating(bool has_oscillating) { this->has_oscillating_ = has_oscillating; } + void set_speed_count(int count) { this->speed_count_ = count; } + void set_preset_modes(const std::set &presets) { this->preset_modes_ = presets; } + fan::FanTraits get_traits() override { return this->traits_; } + + protected: + void control(const fan::FanCall &call) override; + + bool has_oscillating_{false}; + bool has_direction_{false}; + int speed_count_{0}; + fan::FanTraits traits_; + std::set preset_modes_{}; +}; + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index 3ed6b72d8f..cf7d23aec7 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt from esphome.const import ( + CONF_DEVICE_CLASS, CONF_ENTITY_CATEGORY, CONF_FILTERS, CONF_ICON, @@ -14,12 +15,21 @@ from esphome.const import ( CONF_STATE, CONF_FROM, CONF_TO, + DEVICE_CLASS_DATE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_TIMESTAMP, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_generator import MockObjClass from esphome.cpp_helpers import setup_entity from esphome.util import Registry +DEVICE_CLASSES = [ + DEVICE_CLASS_DATE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_TIMESTAMP, +] + IS_PLATFORM_COMPONENT = True @@ -112,10 +122,13 @@ async def map_filter_to_code(config, filter_id): ) +validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") + TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), cv.GenerateID(): cv.declare_id(TextSensor), + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_FILTERS): validate_filters, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { @@ -140,12 +153,21 @@ def text_sensor_schema( *, icon: str = _UNDEF, entity_category: str = _UNDEF, + device_class: str = _UNDEF, ) -> cv.Schema: schema = TEXT_SENSOR_SCHEMA if class_ is not _UNDEF: schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)}) if icon is not _UNDEF: schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) + if device_class is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_DEVICE_CLASS, default=device_class + ): validate_device_class + } + ) if entity_category is not _UNDEF: schema = schema.extend( { @@ -164,6 +186,9 @@ async def build_filters(config): async def setup_text_sensor_core_(var, config): await setup_entity(var, config) + if CONF_DEVICE_CLASS in config: + cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) + if config.get(CONF_FILTERS): # must exist and not be empty filters = await build_filters(config[CONF_FILTERS]) cg.add(var.set_filters(filters)) diff --git a/esphome/components/text_sensor/filter.h b/esphome/components/text_sensor/filter.h index 4e36532945..2de9010b88 100644 --- a/esphome/components/text_sensor/filter.h +++ b/esphome/components/text_sensor/filter.h @@ -28,7 +28,7 @@ class Filter { * @param value The new value. * @return An optional string, the new value that should be pushed out. */ - virtual optional new_value(std::string value); + virtual optional new_value(std::string value) = 0; /// Initialize this filter, please note this can be called more than once. virtual void initialize(TextSensor *parent, Filter *next); diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 996af02f7e..bd72ea70e3 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -13,6 +13,9 @@ namespace text_sensor { #define LOG_TEXT_SENSOR(prefix, type, obj) \ if ((obj) != nullptr) { \ ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ + if (!(obj)->get_device_class().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ + } \ if (!(obj)->get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ @@ -28,7 +31,7 @@ namespace text_sensor { public: \ void set_##name##_text_sensor(text_sensor::TextSensor *text_sensor) { this->name##_text_sensor_ = text_sensor; } -class TextSensor : public EntityBase { +class TextSensor : public EntityBase, public EntityBase_DeviceClass { public: /// Getter-syntax for .state. std::string get_state() const; diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 40a29295f1..26be6ba53a 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -1,6 +1,5 @@ #include "thermostat_climate.h" #include "esphome/core/log.h" -#include namespace esphome { namespace thermostat { @@ -63,6 +62,15 @@ void ThermostatClimate::setup() { this->publish_state(); } +void ThermostatClimate::loop() { + for (auto &timer : this->timer_) { + if (timer.active && (timer.started + timer.time < millis())) { + timer.active = false; + timer.func(); + } + } +} + float ThermostatClimate::cool_deadband() { return this->cooling_deadband_; } float ThermostatClimate::cool_overrun() { return this->cooling_overrun_; } float ThermostatClimate::heat_deadband() { return this->heating_deadband_; } @@ -439,6 +447,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu this->start_timer_(thermostat::TIMER_FANNING_ON); trig_fan = this->fan_only_action_trigger_; } + this->cooling_max_runtime_exceeded_ = false; trig = this->cool_action_trigger_; ESP_LOGVV(TAG, "Switching to COOLING action"); action_ready = true; @@ -452,6 +461,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu this->start_timer_(thermostat::TIMER_FANNING_ON); trig_fan = this->fan_only_action_trigger_; } + this->heating_max_runtime_exceeded_ = false; trig = this->heat_action_trigger_; ESP_LOGVV(TAG, "Switching to HEATING action"); action_ready = true; @@ -752,15 +762,15 @@ bool ThermostatClimate::heating_action_ready_() { void ThermostatClimate::start_timer_(const ThermostatClimateTimerIndex timer_index) { if (this->timer_duration_(timer_index) > 0) { - this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index), - this->timer_cbf_(timer_index)); + this->timer_[timer_index].started = millis(); this->timer_[timer_index].active = true; } } bool ThermostatClimate::cancel_timer_(ThermostatClimateTimerIndex timer_index) { + auto ret = this->timer_[timer_index].active; this->timer_[timer_index].active = false; - return this->cancel_timeout(this->timer_[timer_index].name); + return ret; } bool ThermostatClimate::timer_active_(ThermostatClimateTimerIndex timer_index) { @@ -777,7 +787,6 @@ std::function ThermostatClimate::timer_cbf_(ThermostatClimateTimerIndex void ThermostatClimate::cooling_max_run_time_timer_callback_() { ESP_LOGVV(TAG, "cooling_max_run_time timer expired"); - this->timer_[thermostat::TIMER_COOLING_MAX_RUN_TIME].active = false; this->cooling_max_runtime_exceeded_ = true; this->trigger_supplemental_action_(); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); @@ -785,21 +794,18 @@ void ThermostatClimate::cooling_max_run_time_timer_callback_() { void ThermostatClimate::cooling_off_timer_callback_() { ESP_LOGVV(TAG, "cooling_off timer expired"); - this->timer_[thermostat::TIMER_COOLING_OFF].active = false; this->switch_to_action_(this->compute_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); } void ThermostatClimate::cooling_on_timer_callback_() { ESP_LOGVV(TAG, "cooling_on timer expired"); - this->timer_[thermostat::TIMER_COOLING_ON].active = false; this->switch_to_action_(this->compute_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); } void ThermostatClimate::fan_mode_timer_callback_() { ESP_LOGVV(TAG, "fan_mode timer expired"); - this->timer_[thermostat::TIMER_FAN_MODE].active = false; this->switch_to_fan_mode_(this->fan_mode.value_or(climate::CLIMATE_FAN_ON)); if (this->supports_fan_only_action_uses_fan_mode_timer_) this->switch_to_action_(this->compute_action_()); @@ -807,19 +813,16 @@ void ThermostatClimate::fan_mode_timer_callback_() { void ThermostatClimate::fanning_off_timer_callback_() { ESP_LOGVV(TAG, "fanning_off timer expired"); - this->timer_[thermostat::TIMER_FANNING_OFF].active = false; this->switch_to_action_(this->compute_action_()); } void ThermostatClimate::fanning_on_timer_callback_() { ESP_LOGVV(TAG, "fanning_on timer expired"); - this->timer_[thermostat::TIMER_FANNING_ON].active = false; this->switch_to_action_(this->compute_action_()); } void ThermostatClimate::heating_max_run_time_timer_callback_() { ESP_LOGVV(TAG, "heating_max_run_time timer expired"); - this->timer_[thermostat::TIMER_HEATING_MAX_RUN_TIME].active = false; this->heating_max_runtime_exceeded_ = true; this->trigger_supplemental_action_(); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); @@ -827,21 +830,18 @@ void ThermostatClimate::heating_max_run_time_timer_callback_() { void ThermostatClimate::heating_off_timer_callback_() { ESP_LOGVV(TAG, "heating_off timer expired"); - this->timer_[thermostat::TIMER_HEATING_OFF].active = false; this->switch_to_action_(this->compute_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); } void ThermostatClimate::heating_on_timer_callback_() { ESP_LOGVV(TAG, "heating_on timer expired"); - this->timer_[thermostat::TIMER_HEATING_ON].active = false; this->switch_to_action_(this->compute_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); } void ThermostatClimate::idle_on_timer_callback_() { ESP_LOGVV(TAG, "idle_on timer expired"); - this->timer_[thermostat::TIMER_IDLE_ON].active = false; this->switch_to_action_(this->compute_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); } diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 559812a94f..50510cf070 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -1,10 +1,12 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" #include "esphome/components/climate/climate.h" #include "esphome/components/sensor/sensor.h" +#include #include #include @@ -26,9 +28,9 @@ enum ThermostatClimateTimerIndex : size_t { enum OnBootRestoreFrom : size_t { MEMORY = 0, DEFAULT_PRESET = 1 }; struct ThermostatClimateTimer { - const std::string name; bool active; uint32_t time; + uint32_t started; std::function func; }; @@ -59,6 +61,7 @@ class ThermostatClimate : public climate::Climate, public Component { ThermostatClimate(); void setup() override; void dump_config() override; + void loop() override; void set_default_preset(const std::string &custom_preset); void set_default_preset(climate::ClimatePreset preset); @@ -441,16 +444,17 @@ class ThermostatClimate : public climate::Climate, public Component { /// Climate action timers std::vector timer_{ - {"cool_run", false, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)}, - {"cool_off", false, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)}, - {"cool_on", false, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)}, - {"fan_mode", false, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)}, - {"fan_off", false, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)}, - {"fan_on", false, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)}, - {"heat_run", false, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)}, - {"heat_off", false, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)}, - {"heat_on", false, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)}, - {"idle_on", false, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)}}; + {false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)}, + }; /// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc) std::map preset_config_{}; diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index b2be11611d..2bb3a0cd63 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -37,7 +37,6 @@ time_ns = cg.esphome_ns.namespace("time") RealTimeClock = time_ns.class_("RealTimeClock", cg.PollingComponent) CronTrigger = time_ns.class_("CronTrigger", automation.Trigger.template(), cg.Component) SyncTrigger = time_ns.class_("SyncTrigger", automation.Trigger.template(), cg.Component) -ESPTime = time_ns.struct("ESPTime") TimeHasTimeCondition = time_ns.class_("TimeHasTimeCondition", Condition) diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 0573c7de9d..9b903d098b 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -1,6 +1,10 @@ #include "real_time_clock.h" #include "esphome/core/log.h" +#ifdef USE_HOST +#include +#else #include "lwip/opt.h" +#endif #ifdef USE_ESP8266 #include "sys/time.h" #endif @@ -25,7 +29,7 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) { .tv_sec = static_cast(epoch), .tv_usec = 0, }; ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch); - timezone tz = {0, 0}; + struct timezone tz = {0, 0}; int ret = settimeofday(&timev, &tz); if (ret == EINVAL) { // Some ESP8266 frameworks abort when timezone parameter is not NULL diff --git a/esphome/components/tm1651/__init__.py b/esphome/components/tm1651/__init__.py index a6b2189eb6..4ef8842571 100644 --- a/esphome/components/tm1651/__init__.py +++ b/esphome/components/tm1651/__init__.py @@ -13,6 +13,7 @@ from esphome.const import ( CODEOWNERS = ["@freekode"] tm1651_ns = cg.esphome_ns.namespace("tm1651") +TM1651Brightness = tm1651_ns.enum("TM1651Brightness") TM1651Display = tm1651_ns.class_("TM1651Display", cg.Component) SetLevelPercentAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action) @@ -24,9 +25,9 @@ TurnOffAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action) CONF_LEVEL_PERCENT = "level_percent" TM1651_BRIGHTNESS_OPTIONS = { - 1: TM1651Display.TM1651_BRIGHTNESS_LOW, - 2: TM1651Display.TM1651_BRIGHTNESS_MEDIUM, - 3: TM1651Display.TM1651_BRIGHTNESS_HIGH, + 1: TM1651Brightness.TM1651_BRIGHTNESS_LOW, + 2: TM1651Brightness.TM1651_BRIGHTNESS_MEDIUM, + 3: TM1651Brightness.TM1651_BRIGHTNESS_HIGH, } CONFIG_SCHEMA = cv.All( diff --git a/esphome/components/tm1651/tm1651.cpp b/esphome/components/tm1651/tm1651.cpp index c6bb1bc025..89807f5565 100644 --- a/esphome/components/tm1651/tm1651.cpp +++ b/esphome/components/tm1651/tm1651.cpp @@ -12,9 +12,9 @@ static const char *const TAG = "tm1651.display"; static const uint8_t MAX_INPUT_LEVEL_PERCENT = 100; static const uint8_t TM1651_MAX_LEVEL = 7; -static const uint8_t TM1651_BRIGHTNESS_LOW = 0; -static const uint8_t TM1651_BRIGHTNESS_MEDIUM = 2; -static const uint8_t TM1651_BRIGHTNESS_HIGH = 7; +static const uint8_t TM1651_BRIGHTNESS_LOW_HW = 0; +static const uint8_t TM1651_BRIGHTNESS_MEDIUM_HW = 2; +static const uint8_t TM1651_BRIGHTNESS_HIGH_HW = 7; void TM1651Display::setup() { ESP_LOGCONFIG(TAG, "Setting up TM1651..."); @@ -78,14 +78,14 @@ uint8_t TM1651Display::calculate_level_(uint8_t new_level) { uint8_t TM1651Display::calculate_brightness_(uint8_t new_brightness) { if (new_brightness <= 1) { - return TM1651_BRIGHTNESS_LOW; + return TM1651_BRIGHTNESS_LOW_HW; } else if (new_brightness == 2) { - return TM1651_BRIGHTNESS_MEDIUM; + return TM1651_BRIGHTNESS_MEDIUM_HW; } else if (new_brightness >= 3) { - return TM1651_BRIGHTNESS_HIGH; + return TM1651_BRIGHTNESS_HIGH_HW; } - return TM1651_BRIGHTNESS_LOW; + return TM1651_BRIGHTNESS_LOW_HW; } } // namespace tm1651 diff --git a/esphome/components/tm1651/tm1651.h b/esphome/components/tm1651/tm1651.h index eb65ed186d..fe7b7d9c6f 100644 --- a/esphome/components/tm1651/tm1651.h +++ b/esphome/components/tm1651/tm1651.h @@ -13,6 +13,12 @@ namespace esphome { namespace tm1651 { +enum TM1651Brightness : uint8_t { + TM1651_BRIGHTNESS_LOW = 1, + TM1651_BRIGHTNESS_MEDIUM = 2, + TM1651_BRIGHTNESS_HIGH = 3, +}; + class TM1651Display : public Component { public: void set_clk_pin(InternalGPIOPin *pin) { clk_pin_ = pin; } @@ -24,6 +30,7 @@ class TM1651Display : public Component { void set_level_percent(uint8_t new_level); void set_level(uint8_t new_level); void set_brightness(uint8_t new_brightness); + void set_brightness(TM1651Brightness new_brightness) { this->set_brightness(static_cast(new_brightness)); } void turn_on(); void turn_off(); diff --git a/esphome/components/tmp102/sensor.py b/esphome/components/tmp102/sensor.py index 57d0afd5a1..2cb1a6d1f5 100644 --- a/esphome/components/tmp102/sensor.py +++ b/esphome/components/tmp102/sensor.py @@ -7,6 +7,7 @@ reading temperatures to a resolution of 0.0625°C. https://www.sparkfun.com/datasheets/Sensors/Temperature/tmp102.pdf """ + import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor diff --git a/esphome/components/tmp102/tmp102.cpp b/esphome/components/tmp102/tmp102.cpp index f6bb9a05c0..be60a2d8d4 100644 --- a/esphome/components/tmp102/tmp102.cpp +++ b/esphome/components/tmp102/tmp102.cpp @@ -28,7 +28,7 @@ void TMP102Component::dump_config() { } void TMP102Component::update() { - uint16_t raw_temperature; + int16_t raw_temperature; if (this->write(&TMP102_REGISTER_TEMPERATURE, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; @@ -39,7 +39,6 @@ void TMP102Component::update() { return; } raw_temperature = i2c::i2ctohs(raw_temperature); - raw_temperature = raw_temperature >> 4; float temperature = raw_temperature * TMP102_CONVERSION_FACTOR; ESP_LOGD(TAG, "Got Temperature=%.1f°C", temperature); diff --git a/esphome/components/tmp117/sensor.py b/esphome/components/tmp117/sensor.py index fb97258bc1..82d099cf12 100644 --- a/esphome/components/tmp117/sensor.py +++ b/esphome/components/tmp117/sensor.py @@ -30,37 +30,37 @@ CONFIG_SCHEMA = cv.All( def determine_config_register(polling_period): - if polling_period >= 16.0: + if polling_period >= 16000: # 64 averaged conversions, max conversion time # 0000 00 111 11 00000 # 0000 0011 1110 0000 return 0x03E0 - if polling_period >= 8.0: + if polling_period >= 8000: # 64 averaged conversions, high conversion time # 0000 00 110 11 00000 # 0000 0011 0110 0000 return 0x0360 - if polling_period >= 4.0: + if polling_period >= 4000: # 64 averaged conversions, mid conversion time # 0000 00 101 11 00000 # 0000 0010 1110 0000 return 0x02E0 - if polling_period >= 1.0: + if polling_period >= 1000: # 64 averaged conversions, min conversion time # 0000 00 000 11 00000 # 0000 0000 0110 0000 return 0x0060 - if polling_period >= 0.5: + if polling_period >= 500: # 32 averaged conversions, min conversion time # 0000 00 000 10 00000 # 0000 0000 0100 0000 return 0x0040 - if polling_period >= 0.25: + if polling_period >= 250: # 8 averaged conversions, mid conversion time # 0000 00 010 01 00000 # 0000 0001 0010 0000 return 0x0120 - if polling_period >= 0.125: + if polling_period >= 125: # 8 averaged conversions, min conversion time # 0000 00 000 01 00000 # 0000 0000 0010 0000 @@ -76,5 +76,5 @@ async def to_code(config): await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - update_period = config[CONF_UPDATE_INTERVAL].total_seconds + update_period = config[CONF_UPDATE_INTERVAL].total_milliseconds cg.add(var.set_config(determine_config_register(update_period))) diff --git a/esphome/components/touchscreen/__init__.py b/esphome/components/touchscreen/__init__.py index bc09c6364d..400ba7d5ad 100644 --- a/esphome/components/touchscreen/__init__.py +++ b/esphome/components/touchscreen/__init__.py @@ -3,7 +3,18 @@ import esphome.codegen as cg from esphome.components import display from esphome import automation -from esphome.const import CONF_ON_TOUCH, CONF_ON_RELEASE + +from esphome.const import ( + CONF_ON_TOUCH, + CONF_ON_RELEASE, + CONF_ON_UPDATE, + CONF_SWAP_XY, + CONF_MIRROR_X, + CONF_MIRROR_Y, + CONF_TRANSFORM, + CONF_CALIBRATION, +) + from esphome.core import coroutine_with_priority CODEOWNERS = ["@jesserockz", "@nielsnl68"] @@ -23,29 +34,83 @@ TouchListener = touchscreen_ns.class_("TouchListener") CONF_DISPLAY = "display" CONF_TOUCHSCREEN_ID = "touchscreen_id" CONF_REPORT_INTERVAL = "report_interval" # not used yet: -CONF_ON_UPDATE = "on_update" - -CONF_MIRROR_X = "mirror_x" -CONF_MIRROR_Y = "mirror_y" -CONF_SWAP_XY = "swap_xy" -CONF_TRANSFORM = "transform" +CONF_TOUCH_TIMEOUT = "touch_timeout" -TOUCHSCREEN_SCHEMA = cv.Schema( - { - cv.GenerateID(CONF_DISPLAY): cv.use_id(display.Display), - cv.Optional(CONF_TRANSFORM): cv.Schema( - { - cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, - cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, - cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, - } - ), - cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True), - cv.Optional(CONF_ON_UPDATE): automation.validate_automation(single=True), - cv.Optional(CONF_ON_RELEASE): automation.validate_automation(single=True), - } -).extend(cv.polling_component_schema("50ms")) +CONF_X_MIN = "x_min" +CONF_X_MAX = "x_max" +CONF_Y_MIN = "y_min" +CONF_Y_MAX = "y_max" + + +def validate_calibration(config): + if CONF_CALIBRATION in config: + calibration_config = config[CONF_CALIBRATION] + if ( + cv.int_([CONF_X_MIN]) != 0 + and cv.int_(calibration_config[CONF_X_MAX]) != 0 + and abs( + cv.int_(calibration_config[CONF_X_MIN]) + - cv.int_(calibration_config[CONF_X_MAX]) + ) + < 10 + ): + raise cv.Invalid("Calibration X values difference must be more than 10") + + if ( + cv.int_(calibration_config[CONF_Y_MIN]) != 0 + and cv.int_(calibration_config[CONF_Y_MAX]) != 0 + and abs( + cv.int_(calibration_config[CONF_Y_MIN]) + - cv.int_(calibration_config[CONF_Y_MAX]) + ) + < 10 + ): + raise cv.Invalid("Calibration Y values difference must be more than 10") + + return config + + +def calibration_schema(default_max_values): + return cv.Schema( + { + cv.Optional(CONF_X_MIN, default=0): cv.int_range(min=0, max=4095), + cv.Optional(CONF_X_MAX, default=default_max_values): cv.int_range( + min=0, max=4095 + ), + cv.Optional(CONF_Y_MIN, default=0): cv.int_range(min=0, max=4095), + cv.Optional(CONF_Y_MAX, default=default_max_values): cv.int_range( + min=0, max=4095 + ), + }, + validate_calibration, + ) + + +def touchscreen_schema(default_touch_timeout): + return cv.Schema( + { + cv.GenerateID(CONF_DISPLAY): cv.use_id(display.Display), + cv.Optional(CONF_TRANSFORM): cv.Schema( + { + cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, + } + ), + cv.Optional(CONF_TOUCH_TIMEOUT, default=default_touch_timeout): cv.All( + cv.positive_time_period_milliseconds, + cv.Range(max=cv.TimePeriod(milliseconds=65535)), + ), + cv.Optional(CONF_CALIBRATION): calibration_schema(0), + cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True), + cv.Optional(CONF_ON_UPDATE): automation.validate_automation(single=True), + cv.Optional(CONF_ON_RELEASE): automation.validate_automation(single=True), + } + ).extend(cv.polling_component_schema("50ms")) + + +TOUCHSCREEN_SCHEMA = touchscreen_schema(cv.UNDEFINED) async def register_touchscreen(var, config): @@ -54,12 +119,26 @@ async def register_touchscreen(var, config): disp = await cg.get_variable(config[CONF_DISPLAY]) cg.add(var.set_display(disp)) + if CONF_TOUCH_TIMEOUT in config: + cg.add(var.set_touch_timeout(config[CONF_TOUCH_TIMEOUT])) + if CONF_TRANSFORM in config: transform = config[CONF_TRANSFORM] cg.add(var.set_swap_xy(transform[CONF_SWAP_XY])) cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y])) + if CONF_CALIBRATION in config: + calibration_config = config[CONF_CALIBRATION] + cg.add( + var.set_calibration( + calibration_config[CONF_X_MIN], + calibration_config[CONF_X_MAX], + calibration_config[CONF_Y_MIN], + calibration_config[CONF_Y_MAX], + ) + ) + if CONF_ON_TOUCH in config: await automation.build_automation( var.get_touch_trigger(), diff --git a/esphome/components/touchscreen/touchscreen.cpp b/esphome/components/touchscreen/touchscreen.cpp index f095c2af8c..b9498de152 100644 --- a/esphome/components/touchscreen/touchscreen.cpp +++ b/esphome/components/touchscreen/touchscreen.cpp @@ -13,6 +13,15 @@ void Touchscreen::attach_interrupt_(InternalGPIOPin *irq_pin, esphome::gpio::Int irq_pin->attach_interrupt(TouchscreenInterrupt::gpio_intr, &this->store_, type); this->store_.init = true; this->store_.touched = false; + ESP_LOGD(TAG, "Attach Touch Interupt"); +} + +void Touchscreen::call_setup() { + if (this->display_ != nullptr) { + this->display_width_ = this->display_->get_native_width(); + this->display_height_ = this->display_->get_native_height(); + } + PollingComponent::call_setup(); } void Touchscreen::update() { @@ -20,19 +29,21 @@ void Touchscreen::update() { this->store_.touched = true; } else { // no need to poll if we have interrupts. + ESP_LOGW(TAG, "Touch Polling Stopped. You can safely remove the 'update_interval:' variable from the YAML file."); this->stop_poller(); } } void Touchscreen::loop() { if (this->store_.touched) { + ESP_LOGVV(TAG, "<< Do Touch loop >>"); this->first_touch_ = this->touches_.empty(); this->need_update_ = false; this->is_touched_ = false; this->skip_update_ = false; for (auto &tp : this->touches_) { if (tp.second.state == STATE_PRESSED || tp.second.state == STATE_UPDATED) { - tp.second.state = tp.second.state | STATE_RELEASING; + tp.second.state |= STATE_RELEASING; } else { tp.second.state = STATE_RELEASED; } @@ -42,11 +53,20 @@ void Touchscreen::loop() { this->update_touches(); if (this->skip_update_) { for (auto &tp : this->touches_) { - tp.second.state = tp.second.state & -STATE_RELEASING; + tp.second.state &= ~STATE_RELEASING; } } else { this->store_.touched = false; this->defer([this]() { this->send_touches_(); }); + if (this->touch_timeout_ > 0) { + // Simulate a touch after touch_timeout_> ms. This will reset any existing timeout operation. + // This is to detect touch release. + if (this->is_touched_) { + this->set_timeout(TAG, this->touch_timeout_, [this]() { this->store_.touched = true; }); + } else { + this->cancel_timeout(TAG); + } + } } } } @@ -60,21 +80,25 @@ void Touchscreen::add_raw_touch_position_(uint8_t id, int16_t x_raw, int16_t y_r } else { tp = this->touches_[id]; tp.state = STATE_UPDATED; + tp.y_prev = tp.y; + tp.x_prev = tp.x; } tp.x_raw = x_raw; tp.y_raw = y_raw; tp.z_raw = z_raw; + if (this->x_raw_max_ != this->x_raw_min_ and this->y_raw_max_ != this->y_raw_min_) { + x = this->normalize_(x_raw, this->x_raw_min_, this->x_raw_max_, this->invert_x_); + y = this->normalize_(y_raw, this->y_raw_min_, this->y_raw_max_, this->invert_y_); - x = this->normalize_(x_raw, this->x_raw_min_, this->x_raw_max_, this->invert_x_); - y = this->normalize_(y_raw, this->y_raw_min_, this->y_raw_max_, this->invert_y_); + if (this->swap_x_y_) { + std::swap(x, y); + } - if (this->swap_x_y_) { - std::swap(x, y); + tp.x = (uint16_t) ((int) x * this->display_width_ / 0x1000); + tp.y = (uint16_t) ((int) y * this->display_height_ / 0x1000); + } else { + tp.state |= STATE_CALIBRATE; } - - tp.x = (uint16_t) ((int) x * this->get_width_() / 0x1000); - tp.y = (uint16_t) ((int) y * this->get_height_() / 0x1000); - if (tp.state == STATE_PRESSED) { tp.x_org = tp.x; tp.y_org = tp.y; @@ -89,16 +113,27 @@ void Touchscreen::add_raw_touch_position_(uint8_t id, int16_t x_raw, int16_t y_r } void Touchscreen::send_touches_() { - if (!this->is_touched_) { - this->release_trigger_.trigger(); - for (auto *listener : this->touch_listeners_) - listener->release(); - this->touches_.clear(); - } else { - TouchPoints_t touches; - for (auto tp : this->touches_) { - touches.push_back(tp.second); + TouchPoints_t touches; + ESP_LOGV(TAG, "Touch status: is_touched=%d, was_touched=%d", this->is_touched_, this->was_touched_); + for (auto tp : this->touches_) { + ESP_LOGV(TAG, "Touch status: %d/%d: raw:(%4d,%4d,%4d) calc:(%3d,%4d)", tp.second.id, tp.second.state, + tp.second.x_raw, tp.second.y_raw, tp.second.z_raw, tp.second.x, tp.second.y); + touches.push_back(tp.second); + } + if (this->need_update_ || (!this->is_touched_ && this->was_touched_)) { + this->update_trigger_.trigger(touches); + for (auto *listener : this->touch_listeners_) { + listener->update(touches); } + } + if (!this->is_touched_) { + if (this->was_touched_) { + this->release_trigger_.trigger(); + for (auto *listener : this->touch_listeners_) + listener->release(); + this->touches_.clear(); + } + } else { if (this->first_touch_) { TouchPoint tp = this->touches_.begin()->second; this->touch_trigger_.trigger(tp, touches); @@ -106,13 +141,8 @@ void Touchscreen::send_touches_() { listener->touch(tp); } } - if (this->need_update_) { - this->update_trigger_.trigger(touches); - for (auto *listener : this->touch_listeners_) { - listener->update(touches); - } - } } + this->was_touched_ = this->is_touched_; } int16_t Touchscreen::normalize_(int16_t val, int16_t min_val, int16_t max_val, bool inverted) { diff --git a/esphome/components/touchscreen/touchscreen.h b/esphome/components/touchscreen/touchscreen.h index 74747c589c..21111f87b3 100644 --- a/esphome/components/touchscreen/touchscreen.h +++ b/esphome/components/touchscreen/touchscreen.h @@ -16,6 +16,7 @@ static const uint8_t STATE_RELEASED = 0x00; static const uint8_t STATE_PRESSED = 0x01; static const uint8_t STATE_UPDATED = 0x02; static const uint8_t STATE_RELEASING = 0x04; +static const uint8_t STATE_CALIBRATE = 0x07; struct TouchPoint { uint8_t id; @@ -46,6 +47,7 @@ class Touchscreen : public PollingComponent { void set_display(display::Display *display) { this->display_ = display; } display::Display *get_display() const { return this->display_; } + void set_touch_timeout(uint16_t val) { this->touch_timeout_ = val; } void set_mirror_x(bool invert_x) { this->invert_x_ = invert_x; } void set_mirror_y(bool invert_y) { this->invert_y_ = invert_y; } void set_swap_xy(bool swap) { this->swap_x_y_ = swap; } @@ -67,8 +69,6 @@ class Touchscreen : public PollingComponent { void register_listener(TouchListener *listener) { this->touch_listeners_.push_back(listener); } - virtual void update_touches() = 0; - optional get_touch() { return this->touches_.begin()->second; } TouchPoints_t get_touches() { @@ -81,6 +81,7 @@ class Touchscreen : public PollingComponent { void update() override; void loop() override; + void call_setup() override; protected: /// Call this function to send touch points to the `on_touch` listener and the binary_sensors. @@ -89,17 +90,18 @@ class Touchscreen : public PollingComponent { void add_raw_touch_position_(uint8_t id, int16_t x_raw, int16_t y_raw, int16_t z_raw = 0); + virtual void update_touches() = 0; + void send_touches_(); int16_t normalize_(int16_t val, int16_t min_val, int16_t max_val, bool inverted = false); - uint16_t get_width_() { return this->display_->get_width(); } - - uint16_t get_height_() { return this->display_->get_height(); } - display::Display *display_{nullptr}; int16_t x_raw_min_{0}, x_raw_max_{0}, y_raw_min_{0}, y_raw_max_{0}; + int16_t display_width_{0}, display_height_{0}; + + uint16_t touch_timeout_{0}; bool invert_x_{false}, invert_y_{false}, swap_x_y_{false}; Trigger touch_trigger_; @@ -113,6 +115,7 @@ class Touchscreen : public PollingComponent { bool first_touch_{true}; bool need_update_{false}; bool is_touched_{false}; + bool was_touched_{false}; bool skip_update_{false}; }; diff --git a/esphome/components/tt21100/touchscreen/__init__.py b/esphome/components/tt21100/touchscreen/__init__.py index 4458ad0974..510ca2df3a 100644 --- a/esphome/components/tt21100/touchscreen/__init__.py +++ b/esphome/components/tt21100/touchscreen/__init__.py @@ -20,7 +20,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( cv.Schema( { cv.GenerateID(): cv.declare_id(TT21100Touchscreen), - cv.Required(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, } ).extend(i2c.i2c_device_schema(0x24)) @@ -32,8 +32,9 @@ async def to_code(config): await touchscreen.register_touchscreen(var, config) await i2c.register_i2c_device(var, config) - interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) - cg.add(var.set_interrupt_pin(interrupt_pin)) + if CONF_INTERRUPT_PIN in config: + interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) + cg.add(var.set_interrupt_pin(interrupt_pin)) if CONF_RESET_PIN in config: rts_pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) diff --git a/esphome/components/tt21100/touchscreen/tt21100.cpp b/esphome/components/tt21100/touchscreen/tt21100.cpp index 6b5cba74cd..2bea72a59e 100644 --- a/esphome/components/tt21100/touchscreen/tt21100.cpp +++ b/esphome/components/tt21100/touchscreen/tt21100.cpp @@ -50,10 +50,11 @@ void TT21100Touchscreen::setup() { ESP_LOGCONFIG(TAG, "Setting up TT21100 Touchscreen..."); // Register interrupt pin - this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); - this->interrupt_pin_->setup(); - - this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); + if (this->interrupt_pin_ != nullptr) { + this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + this->interrupt_pin_->setup(); + this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); + } // Perform reset if necessary if (this->reset_pin_ != nullptr) { @@ -62,8 +63,17 @@ void TT21100Touchscreen::setup() { } // Update display dimensions if they were updated during display setup - this->x_raw_max_ = this->get_width_(); - this->y_raw_max_ = this->get_height_(); + if (this->display_ != nullptr) { + if (this->x_raw_max_ == this->x_raw_min_) { + this->x_raw_max_ = this->display_->get_native_width(); + } + if (this->y_raw_max_ == this->y_raw_min_) { + this->x_raw_max_ = this->display_->get_native_height(); + } + } + + // Trigger initial read to activate the interrupt + this->store_.touched = true; } void TT21100Touchscreen::update_touches() { diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index 199c2eabeb..b3d401e5a4 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -7,15 +7,22 @@ from esphome.const import ( CONF_SWITCH_DATAPOINT, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, + CONF_PRESET, + CONF_SWING_MODE, + CONF_FAN_MODE, + CONF_TEMPERATURE, ) from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] CODEOWNERS = ["@jesserockz"] -CONF_ACTIVE_STATE_DATAPOINT = "active_state_datapoint" -CONF_ACTIVE_STATE_HEATING_VALUE = "active_state_heating_value" -CONF_ACTIVE_STATE_COOLING_VALUE = "active_state_cooling_value" +CONF_ACTIVE_STATE = "active_state" +CONF_DATAPOINT = "datapoint" +CONF_HEATING_VALUE = "heating_value" +CONF_COOLING_VALUE = "cooling_value" +CONF_DRYING_VALUE = "drying_value" +CONF_FANONLY_VALUE = "fanonly_value" CONF_HEATING_STATE_PIN = "heating_state_pin" CONF_COOLING_STATE_PIN = "cooling_state_pin" CONF_TARGET_TEMPERATURE_DATAPOINT = "target_temperature_datapoint" @@ -23,9 +30,17 @@ CONF_CURRENT_TEMPERATURE_DATAPOINT = "current_temperature_datapoint" CONF_TEMPERATURE_MULTIPLIER = "temperature_multiplier" CONF_CURRENT_TEMPERATURE_MULTIPLIER = "current_temperature_multiplier" CONF_TARGET_TEMPERATURE_MULTIPLIER = "target_temperature_multiplier" -CONF_ECO_DATAPOINT = "eco_datapoint" -CONF_ECO_TEMPERATURE = "eco_temperature" +CONF_ECO = "eco" +CONF_SLEEP = "sleep" +CONF_SLEEP_DATAPOINT = "sleep_datapoint" CONF_REPORTS_FAHRENHEIT = "reports_fahrenheit" +CONF_VERTICAL_DATAPOINT = "vertical_datapoint" +CONF_HORIZONTAL_DATAPOINT = "horizontal_datapoint" +CONF_LOW_VALUE = "low_value" +CONF_MEDIUM_VALUE = "medium_value" +CONF_MIDDLE_VALUE = "middle_value" +CONF_HIGH_VALUE = "high_value" +CONF_AUTO_VALUE = "auto_value" TuyaClimate = tuya_ns.class_("TuyaClimate", climate.Climate, cg.Component) @@ -67,30 +82,73 @@ def validate_temperature_multipliers(value): return value -def validate_active_state_values(value): - if CONF_ACTIVE_STATE_DATAPOINT not in value: - if CONF_ACTIVE_STATE_COOLING_VALUE in value: - raise cv.Invalid( - f"{CONF_ACTIVE_STATE_DATAPOINT} required if using " - f"{CONF_ACTIVE_STATE_COOLING_VALUE}" - ) - else: - if value[CONF_SUPPORTS_COOL] and CONF_ACTIVE_STATE_COOLING_VALUE not in value: - raise cv.Invalid( - f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using " - f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling" - ) +def validate_cooling_values(value): + if CONF_SUPPORTS_COOL in value: + cooling_supported = value[CONF_SUPPORTS_COOL] + if not cooling_supported and CONF_ACTIVE_STATE in value: + active_state_config = value[CONF_ACTIVE_STATE] + if ( + CONF_COOLING_VALUE in active_state_config + or CONF_COOLING_STATE_PIN in value + ): + raise cv.Invalid( + f"Device does not support cooling, but {CONF_COOLING_VALUE} or {CONF_COOLING_STATE_PIN} specified." + f" Please add '{CONF_SUPPORTS_COOL}: true' to your configuration." + ) + elif cooling_supported and CONF_ACTIVE_STATE in value: + active_state_config = value[CONF_ACTIVE_STATE] + if ( + CONF_COOLING_VALUE not in active_state_config + and CONF_COOLING_STATE_PIN not in value + ): + raise cv.Invalid( + f"Either {CONF_ACTIVE_STATE} {CONF_COOLING_VALUE} or {CONF_COOLING_STATE_PIN} is required if" + f" {CONF_SUPPORTS_COOL}: true' is in your configuration." + ) return value -def validate_eco_values(value): - if CONF_ECO_TEMPERATURE in value and CONF_ECO_DATAPOINT not in value: - raise cv.Invalid( - f"{CONF_ECO_DATAPOINT} required if using {CONF_ECO_TEMPERATURE}" - ) - return value +ACTIVE_STATES = cv.Schema( + { + cv.Required(CONF_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_HEATING_VALUE, default=1): cv.uint8_t, + cv.Optional(CONF_COOLING_VALUE): cv.uint8_t, + cv.Optional(CONF_DRYING_VALUE): cv.uint8_t, + cv.Optional(CONF_FANONLY_VALUE): cv.uint8_t, + }, +) +PRESETS = cv.Schema( + { + cv.Optional(CONF_ECO): { + cv.Required(CONF_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_TEMPERATURE): cv.temperature, + }, + cv.Optional(CONF_SLEEP): { + cv.Required(CONF_DATAPOINT): cv.uint8_t, + }, + }, +) + +FAN_MODES = cv.Schema( + { + cv.Required(CONF_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_AUTO_VALUE): cv.uint8_t, + cv.Optional(CONF_LOW_VALUE): cv.uint8_t, + cv.Optional(CONF_MEDIUM_VALUE): cv.uint8_t, + cv.Optional(CONF_MIDDLE_VALUE): cv.uint8_t, + cv.Optional(CONF_HIGH_VALUE): cv.uint8_t, + } +) + +SWING_MODES = cv.Schema( + { + cv.Optional(CONF_VERTICAL_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_HORIZONTAL_DATAPOINT): cv.uint8_t, + }, +) + CONFIG_SCHEMA = cv.All( climate.CLIMATE_SCHEMA.extend( { @@ -99,9 +157,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, cv.Optional(CONF_SUPPORTS_COOL, default=False): cv.boolean, cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_ACTIVE_STATE_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_ACTIVE_STATE_HEATING_VALUE, default=1): cv.uint8_t, - cv.Optional(CONF_ACTIVE_STATE_COOLING_VALUE): cv.uint8_t, + cv.Optional(CONF_ACTIVE_STATE): ACTIVE_STATES, cv.Optional(CONF_HEATING_STATE_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_COOLING_STATE_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_TARGET_TEMPERATURE_DATAPOINT): cv.uint8_t, @@ -109,17 +165,32 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_TEMPERATURE_MULTIPLIER): cv.positive_float, cv.Optional(CONF_CURRENT_TEMPERATURE_MULTIPLIER): cv.positive_float, cv.Optional(CONF_TARGET_TEMPERATURE_MULTIPLIER): cv.positive_float, - cv.Optional(CONF_ECO_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_ECO_TEMPERATURE): cv.temperature, cv.Optional(CONF_REPORTS_FAHRENHEIT, default=False): cv.boolean, + cv.Optional(CONF_PRESET): PRESETS, + cv.Optional(CONF_FAN_MODE): FAN_MODES, + cv.Optional(CONF_SWING_MODE): SWING_MODES, + cv.Optional("active_state_datapoint"): cv.invalid( + "'active_state_datapoint' has been moved inside of the 'active_state' config block as 'datapoint'" + ), + cv.Optional("active_state_heating_value"): cv.invalid( + "'active_state_heating_value' has been moved inside of the 'active_state' config block as 'heating_value'" + ), + cv.Optional("active_state_cooling_value"): cv.invalid( + "'active_state_cooling_value' has been moved inside of the 'active_state' config block as 'cooling_value'" + ), + cv.Optional("eco_datapoint"): cv.invalid( + "'eco_datapoint' has been moved inside of the 'eco' config block under 'preset' as 'datapoint'" + ), + cv.Optional("eco_temperature"): cv.invalid( + "'eco_temperature' has been moved inside of the 'eco' config block under 'preset' as 'temperature'" + ), } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT), validate_temperature_multipliers, - validate_active_state_values, - cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_HEATING_STATE_PIN), - cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_COOLING_STATE_PIN), - validate_eco_values, + validate_cooling_values, + cv.has_at_most_one_key(CONF_ACTIVE_STATE, CONF_HEATING_STATE_PIN), + cv.has_at_most_one_key(CONF_ACTIVE_STATE, CONF_COOLING_STATE_PIN), ) @@ -133,61 +204,78 @@ async def to_code(config): cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT])) cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL])) - if CONF_SWITCH_DATAPOINT in config: - cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT])) - if CONF_ACTIVE_STATE_DATAPOINT in config: - cg.add(var.set_active_state_id(config[CONF_ACTIVE_STATE_DATAPOINT])) - if CONF_ACTIVE_STATE_HEATING_VALUE in config: - cg.add( - var.set_active_state_heating_value( - config[CONF_ACTIVE_STATE_HEATING_VALUE] - ) - ) - if CONF_ACTIVE_STATE_COOLING_VALUE in config: - cg.add( - var.set_active_state_cooling_value( - config[CONF_ACTIVE_STATE_COOLING_VALUE] - ) - ) + if switch_datapoint := config.get(CONF_SWITCH_DATAPOINT): + cg.add(var.set_switch_id(switch_datapoint)) + + if active_state_config := config.get(CONF_ACTIVE_STATE): + cg.add(var.set_active_state_id(CONF_DATAPOINT)) + if (heating_value := active_state_config.get(CONF_HEATING_VALUE)) is not None: + cg.add(var.set_active_state_heating_value(heating_value)) + if (cooling_value := active_state_config.get(CONF_COOLING_VALUE)) is not None: + cg.add(var.set_active_state_cooling_value(cooling_value)) + if (drying_value := active_state_config.get(CONF_DRYING_VALUE)) is not None: + cg.add(var.set_active_state_drying_value(drying_value)) + if (fanonly_value := active_state_config.get(CONF_FANONLY_VALUE)) is not None: + cg.add(var.set_active_state_fanonly_value(fanonly_value)) else: - if CONF_HEATING_STATE_PIN in config: + if heating_state_pin_config := config.get(CONF_HEATING_STATE_PIN): heating_state_pin = await cg.gpio_pin_expression( - config[CONF_HEATING_STATE_PIN] + config(heating_state_pin_config) ) cg.add(var.set_heating_state_pin(heating_state_pin)) - if CONF_COOLING_STATE_PIN in config: + if cooling_state_pin_config := config.get(CONF_COOLING_STATE_PIN): cooling_state_pin = await cg.gpio_pin_expression( - config[CONF_COOLING_STATE_PIN] + config(cooling_state_pin_config) ) cg.add(var.set_cooling_state_pin(cooling_state_pin)) - if CONF_TARGET_TEMPERATURE_DATAPOINT in config: - cg.add(var.set_target_temperature_id(config[CONF_TARGET_TEMPERATURE_DATAPOINT])) - if CONF_CURRENT_TEMPERATURE_DATAPOINT in config: - cg.add( - var.set_current_temperature_id(config[CONF_CURRENT_TEMPERATURE_DATAPOINT]) - ) - if CONF_TEMPERATURE_MULTIPLIER in config: - cg.add( - var.set_target_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER]) - ) - cg.add( - var.set_current_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER]) - ) + + if target_temperature_datapoint := config.get(CONF_TARGET_TEMPERATURE_DATAPOINT): + cg.add(var.set_target_temperature_id(target_temperature_datapoint)) + if current_temperature_datapoint := config.get(CONF_CURRENT_TEMPERATURE_DATAPOINT): + cg.add(var.set_current_temperature_id(current_temperature_datapoint)) + + if temperature_multiplier := config.get(CONF_TEMPERATURE_MULTIPLIER): + cg.add(var.set_target_temperature_multiplier(temperature_multiplier)) + cg.add(var.set_current_temperature_multiplier(temperature_multiplier)) else: - cg.add( - var.set_current_temperature_multiplier( - config[CONF_CURRENT_TEMPERATURE_MULTIPLIER] + if current_temperature_multiplier := config.get( + CONF_CURRENT_TEMPERATURE_MULTIPLIER + ): + cg.add( + var.set_current_temperature_multiplier(current_temperature_multiplier) ) - ) - cg.add( - var.set_target_temperature_multiplier( - config[CONF_TARGET_TEMPERATURE_MULTIPLIER] - ) - ) - if CONF_ECO_DATAPOINT in config: - cg.add(var.set_eco_id(config[CONF_ECO_DATAPOINT])) - if CONF_ECO_TEMPERATURE in config: - cg.add(var.set_eco_temperature(config[CONF_ECO_TEMPERATURE])) + if target_temperature_multiplier := config.get( + CONF_TARGET_TEMPERATURE_MULTIPLIER + ): + cg.add(var.set_target_temperature_multiplier(target_temperature_multiplier)) if config[CONF_REPORTS_FAHRENHEIT]: cg.add(var.set_reports_fahrenheit()) + + if preset_config := config.get(CONF_PRESET, {}): + if eco_config := preset_config.get(CONF_ECO, {}): + cg.add(var.set_eco_id(CONF_DATAPOINT)) + if eco_temperature := eco_config.get(CONF_TEMPERATURE): + cg.add(var.set_eco_temperature(eco_temperature)) + if CONF_SLEEP in preset_config: + cg.add(var.set_sleep_id(CONF_DATAPOINT)) + + if swing_mode_config := config.get(CONF_SWING_MODE): + if swing_vertical_datapoint := swing_mode_config.get(CONF_VERTICAL_DATAPOINT): + cg.add(var.set_swing_vertical_id(swing_vertical_datapoint)) + if swing_horizontal_datapoint := swing_mode_config.get( + CONF_HORIZONTAL_DATAPOINT + ): + cg.add(var.set_swing_horizontal_id(swing_horizontal_datapoint)) + if fan_mode_config := config.get(CONF_FAN_MODE): + cg.add(var.set_fan_speed_id(CONF_DATAPOINT)) + if (fan_auto_value := fan_mode_config.get(CONF_AUTO_VALUE)) is not None: + cg.add(var.set_fan_speed_auto_value(fan_auto_value)) + if (fan_low_value := fan_mode_config.get(CONF_LOW_VALUE)) is not None: + cg.add(var.set_fan_speed_low_value(fan_low_value)) + if (fan_medium_value := fan_mode_config.get(CONF_MEDIUM_VALUE)) is not None: + cg.add(var.set_fan_speed_medium_value(fan_medium_value)) + if (fan_middle_value := fan_mode_config.get(CONF_MIDDLE_VALUE)) is not None: + cg.add(var.set_fan_speed_middle_value(fan_middle_value)) + if (fan_high_value := fan_mode_config.get(CONF_HIGH_VALUE)) is not None: + cg.add(var.set_fan_speed_high_value(fan_high_value)) diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 687764e30f..274e19a69e 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -75,6 +75,41 @@ void TuyaClimate::setup() { this->publish_state(); }); } + if (this->sleep_id_.has_value()) { + this->parent_->register_listener(*this->sleep_id_, [this](const TuyaDatapoint &datapoint) { + this->sleep_ = datapoint.value_bool; + ESP_LOGV(TAG, "MCU reported sleep is: %s", ONOFF(this->sleep_)); + this->compute_preset_(); + this->compute_target_temperature_(); + this->publish_state(); + }); + } + if (this->swing_vertical_id_.has_value()) { + this->parent_->register_listener(*this->swing_vertical_id_, [this](const TuyaDatapoint &datapoint) { + this->swing_vertical_ = datapoint.value_bool; + ESP_LOGV(TAG, "MCU reported vertical swing is: %s", ONOFF(datapoint.value_bool)); + this->compute_swingmode_(); + this->publish_state(); + }); + } + + if (this->swing_horizontal_id_.has_value()) { + this->parent_->register_listener(*this->swing_horizontal_id_, [this](const TuyaDatapoint &datapoint) { + this->swing_horizontal_ = datapoint.value_bool; + ESP_LOGV(TAG, "MCU reported horizontal swing is: %s", ONOFF(datapoint.value_bool)); + this->compute_swingmode_(); + this->publish_state(); + }); + } + + if (this->fan_speed_id_.has_value()) { + this->parent_->register_listener(*this->fan_speed_id_, [this](const TuyaDatapoint &datapoint) { + ESP_LOGV(TAG, "MCU reported Fan Speed Mode is: %u", datapoint.value_enum); + this->fan_state_ = datapoint.value_enum; + this->compute_fanmode_(); + this->publish_state(); + }); + } } void TuyaClimate::loop() { @@ -110,8 +145,22 @@ void TuyaClimate::control(const climate::ClimateCall &call) { const bool switch_state = *call.get_mode() != climate::CLIMATE_MODE_OFF; ESP_LOGV(TAG, "Setting switch: %s", ONOFF(switch_state)); this->parent_->set_boolean_datapoint_value(*this->switch_id_, switch_state); + const climate::ClimateMode new_mode = *call.get_mode(); + + if (new_mode == climate::CLIMATE_MODE_HEAT && this->supports_heat_) { + this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_heating_value_); + } else if (new_mode == climate::CLIMATE_MODE_COOL && this->supports_cool_) { + this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_cooling_value_); + } else if (new_mode == climate::CLIMATE_MODE_DRY && this->active_state_drying_value_.has_value()) { + this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_drying_value_); + } else if (new_mode == climate::CLIMATE_MODE_FAN_ONLY && this->active_state_fanonly_value_.has_value()) { + this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_fanonly_value_); + } } + control_swing_mode_(call); + control_fan_mode_(call); + if (call.get_target_temperature().has_value()) { float target_temperature = *call.get_target_temperature(); if (this->reports_fahrenheit_) @@ -129,6 +178,106 @@ void TuyaClimate::control(const climate::ClimateCall &call) { ESP_LOGV(TAG, "Setting eco: %s", ONOFF(eco)); this->parent_->set_boolean_datapoint_value(*this->eco_id_, eco); } + if (this->sleep_id_.has_value()) { + const bool sleep = preset == climate::CLIMATE_PRESET_SLEEP; + ESP_LOGV(TAG, "Setting sleep: %s", ONOFF(sleep)); + this->parent_->set_boolean_datapoint_value(*this->sleep_id_, sleep); + } + } +} + +void TuyaClimate::control_swing_mode_(const climate::ClimateCall &call) { + bool vertical_swing_changed = false; + bool horizontal_swing_changed = false; + + if (call.get_swing_mode().has_value()) { + const auto swing_mode = *call.get_swing_mode(); + + switch (swing_mode) { + case climate::CLIMATE_SWING_OFF: + if (swing_vertical_ || swing_horizontal_) { + this->swing_vertical_ = false; + this->swing_horizontal_ = false; + vertical_swing_changed = true; + horizontal_swing_changed = true; + } + break; + + case climate::CLIMATE_SWING_BOTH: + if (!swing_vertical_ || !swing_horizontal_) { + this->swing_vertical_ = true; + this->swing_horizontal_ = true; + vertical_swing_changed = true; + horizontal_swing_changed = true; + } + break; + + case climate::CLIMATE_SWING_VERTICAL: + if (!swing_vertical_ || swing_horizontal_) { + this->swing_vertical_ = true; + this->swing_horizontal_ = false; + vertical_swing_changed = true; + horizontal_swing_changed = true; + } + break; + + case climate::CLIMATE_SWING_HORIZONTAL: + if (swing_vertical_ || !swing_horizontal_) { + this->swing_vertical_ = false; + this->swing_horizontal_ = true; + vertical_swing_changed = true; + horizontal_swing_changed = true; + } + break; + + default: + break; + } + } + + if (vertical_swing_changed && this->swing_vertical_id_.has_value()) { + ESP_LOGV(TAG, "Setting vertical swing: %s", ONOFF(swing_vertical_)); + this->parent_->set_boolean_datapoint_value(*this->swing_vertical_id_, swing_vertical_); + } + + if (horizontal_swing_changed && this->swing_horizontal_id_.has_value()) { + ESP_LOGV(TAG, "Setting horizontal swing: %s", ONOFF(swing_horizontal_)); + this->parent_->set_boolean_datapoint_value(*this->swing_horizontal_id_, swing_horizontal_); + } + + // Publish the state after updating the swing mode + this->publish_state(); +} + +void TuyaClimate::control_fan_mode_(const climate::ClimateCall &call) { + if (call.get_fan_mode().has_value()) { + climate::ClimateFanMode fan_mode = *call.get_fan_mode(); + + uint8_t tuya_fan_speed; + switch (fan_mode) { + case climate::CLIMATE_FAN_LOW: + tuya_fan_speed = *fan_speed_low_value_; + break; + case climate::CLIMATE_FAN_MEDIUM: + tuya_fan_speed = *fan_speed_medium_value_; + break; + case climate::CLIMATE_FAN_MIDDLE: + tuya_fan_speed = *fan_speed_middle_value_; + break; + case climate::CLIMATE_FAN_HIGH: + tuya_fan_speed = *fan_speed_high_value_; + break; + case climate::CLIMATE_FAN_AUTO: + tuya_fan_speed = *fan_speed_auto_value_; + break; + default: + tuya_fan_speed = 0; + break; + } + + if (this->fan_speed_id_.has_value()) { + this->parent_->set_enum_datapoint_value(*this->fan_speed_id_, tuya_fan_speed); + } } } @@ -140,10 +289,46 @@ climate::ClimateTraits TuyaClimate::traits() { traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); if (supports_cool_) traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (this->active_state_drying_value_.has_value()) + traits.add_supported_mode(climate::CLIMATE_MODE_DRY); + if (this->active_state_fanonly_value_.has_value()) + traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); if (this->eco_id_.has_value()) { - traits.add_supported_preset(climate::CLIMATE_PRESET_NONE); traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); } + if (this->sleep_id_.has_value()) { + traits.add_supported_preset(climate::CLIMATE_PRESET_SLEEP); + } + if (this->sleep_id_.has_value() || this->eco_id_.has_value()) { + traits.add_supported_preset(climate::CLIMATE_PRESET_NONE); + } + if (this->swing_vertical_id_.has_value() && this->swing_horizontal_id_.has_value()) { + std::set supported_swing_modes = { + climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL, + climate::CLIMATE_SWING_HORIZONTAL}; + traits.set_supported_swing_modes(std::move(supported_swing_modes)); + } else if (this->swing_vertical_id_.has_value()) { + std::set supported_swing_modes = {climate::CLIMATE_SWING_OFF, + climate::CLIMATE_SWING_VERTICAL}; + traits.set_supported_swing_modes(std::move(supported_swing_modes)); + } else if (this->swing_horizontal_id_.has_value()) { + std::set supported_swing_modes = {climate::CLIMATE_SWING_OFF, + climate::CLIMATE_SWING_HORIZONTAL}; + traits.set_supported_swing_modes(std::move(supported_swing_modes)); + } + + if (fan_speed_id_) { + if (fan_speed_low_value_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_LOW); + if (fan_speed_medium_value_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MEDIUM); + if (fan_speed_middle_value_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE); + if (fan_speed_high_value_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_HIGH); + if (fan_speed_auto_value_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_AUTO); + } return traits; } @@ -166,16 +351,56 @@ void TuyaClimate::dump_config() { if (this->eco_id_.has_value()) { ESP_LOGCONFIG(TAG, " Eco has datapoint ID %u", *this->eco_id_); } + if (this->sleep_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Sleep has datapoint ID %u", *this->sleep_id_); + } + if (this->swing_vertical_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Swing Vertical has datapoint ID %u", *this->swing_vertical_id_); + } + if (this->swing_horizontal_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Swing Horizontal has datapoint ID %u", *this->swing_horizontal_id_); + } } void TuyaClimate::compute_preset_() { if (this->eco_) { this->preset = climate::CLIMATE_PRESET_ECO; + } else if (this->sleep_) { + this->preset = climate::CLIMATE_PRESET_SLEEP; } else { this->preset = climate::CLIMATE_PRESET_NONE; } } +void TuyaClimate::compute_swingmode_() { + if (this->swing_vertical_ && this->swing_horizontal_) { + this->swing_mode = climate::CLIMATE_SWING_BOTH; + } else if (this->swing_vertical_) { + this->swing_mode = climate::CLIMATE_SWING_VERTICAL; + } else if (this->swing_horizontal_) { + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + } else { + this->swing_mode = climate::CLIMATE_SWING_OFF; + } +} + +void TuyaClimate::compute_fanmode_() { + if (this->fan_speed_id_.has_value()) { + // Use state from MCU datapoint + if (this->fan_speed_auto_value_.has_value() && this->fan_state_ == this->fan_speed_auto_value_) { + this->fan_mode = climate::CLIMATE_FAN_AUTO; + } else if (this->fan_speed_high_value_.has_value() && this->fan_state_ == this->fan_speed_high_value_) { + this->fan_mode = climate::CLIMATE_FAN_HIGH; + } else if (this->fan_speed_medium_value_.has_value() && this->fan_state_ == this->fan_speed_medium_value_) { + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + } else if (this->fan_speed_middle_value_.has_value() && this->fan_state_ == this->fan_speed_middle_value_) { + this->fan_mode = climate::CLIMATE_FAN_MIDDLE; + } else if (this->fan_speed_low_value_.has_value() && this->fan_state_ == this->fan_speed_low_value_) { + this->fan_mode = climate::CLIMATE_FAN_LOW; + } + } +} + void TuyaClimate::compute_target_temperature_() { if (this->eco_ && this->eco_temperature_.has_value()) { this->target_temperature = *this->eco_temperature_; @@ -202,16 +427,28 @@ void TuyaClimate::compute_state_() { if (this->supports_heat_ && this->active_state_heating_value_.has_value() && this->active_state_ == this->active_state_heating_value_) { target_action = climate::CLIMATE_ACTION_HEATING; + this->mode = climate::CLIMATE_MODE_HEAT; } else if (this->supports_cool_ && this->active_state_cooling_value_.has_value() && this->active_state_ == this->active_state_cooling_value_) { target_action = climate::CLIMATE_ACTION_COOLING; + this->mode = climate::CLIMATE_MODE_COOL; + } else if (this->active_state_drying_value_.has_value() && + this->active_state_ == this->active_state_drying_value_) { + target_action = climate::CLIMATE_ACTION_DRYING; + this->mode = climate::CLIMATE_MODE_DRY; + } else if (this->active_state_fanonly_value_.has_value() && + this->active_state_ == this->active_state_fanonly_value_) { + target_action = climate::CLIMATE_ACTION_FAN; + this->mode = climate::CLIMATE_MODE_FAN_ONLY; } } else if (this->heating_state_pin_ != nullptr || this->cooling_state_pin_ != nullptr) { // Use state from input pins if (this->heating_state_) { target_action = climate::CLIMATE_ACTION_HEATING; + this->mode = climate::CLIMATE_MODE_HEAT; } else if (this->cooling_state_) { target_action = climate::CLIMATE_ACTION_COOLING; + this->mode = climate::CLIMATE_MODE_COOL; } } else { // Fallback to active state calc based on temp and hysteresis @@ -219,8 +456,10 @@ void TuyaClimate::compute_state_() { if (std::abs(temp_diff) > this->hysteresis_) { if (this->supports_heat_ && temp_diff > 0) { target_action = climate::CLIMATE_ACTION_HEATING; + this->mode = climate::CLIMATE_MODE_HEAT; } else if (this->supports_cool_ && temp_diff < 0) { target_action = climate::CLIMATE_ACTION_COOLING; + this->mode = climate::CLIMATE_MODE_COOL; } } } diff --git a/esphome/components/tuya/climate/tuya_climate.h b/esphome/components/tuya/climate/tuya_climate.h index 7c18625c4e..d6258c21e1 100644 --- a/esphome/components/tuya/climate/tuya_climate.h +++ b/esphome/components/tuya/climate/tuya_climate.h @@ -18,8 +18,22 @@ class TuyaClimate : public climate::Climate, public Component { void set_active_state_id(uint8_t state_id) { this->active_state_id_ = state_id; } void set_active_state_heating_value(uint8_t value) { this->active_state_heating_value_ = value; } void set_active_state_cooling_value(uint8_t value) { this->active_state_cooling_value_ = value; } + void set_active_state_drying_value(uint8_t value) { this->active_state_drying_value_ = value; } + void set_active_state_fanonly_value(uint8_t value) { this->active_state_fanonly_value_ = value; } void set_heating_state_pin(GPIOPin *pin) { this->heating_state_pin_ = pin; } void set_cooling_state_pin(GPIOPin *pin) { this->cooling_state_pin_ = pin; } + void set_swing_vertical_id(uint8_t swing_vertical_id) { this->swing_vertical_id_ = swing_vertical_id; } + void set_swing_horizontal_id(uint8_t swing_horizontal_id) { this->swing_horizontal_id_ = swing_horizontal_id; } + void set_fan_speed_id(uint8_t fan_speed_id) { this->fan_speed_id_ = fan_speed_id; } + void set_fan_speed_low_value(uint8_t fan_speed_low_value) { this->fan_speed_low_value_ = fan_speed_low_value; } + void set_fan_speed_medium_value(uint8_t fan_speed_medium_value) { + this->fan_speed_medium_value_ = fan_speed_medium_value; + } + void set_fan_speed_middle_value(uint8_t fan_speed_middle_value) { + this->fan_speed_middle_value_ = fan_speed_middle_value; + } + void set_fan_speed_high_value(uint8_t fan_speed_high_value) { this->fan_speed_high_value_ = fan_speed_high_value; } + void set_fan_speed_auto_value(uint8_t fan_speed_auto_value) { this->fan_speed_auto_value_ = fan_speed_auto_value; } void set_target_temperature_id(uint8_t target_temperature_id) { this->target_temperature_id_ = target_temperature_id; } @@ -34,6 +48,7 @@ class TuyaClimate : public climate::Climate, public Component { } void set_eco_id(uint8_t eco_id) { this->eco_id_ = eco_id; } void set_eco_temperature(float eco_temperature) { this->eco_temperature_ = eco_temperature; } + void set_sleep_id(uint8_t sleep_id) { this->sleep_id_ = sleep_id; } void set_reports_fahrenheit() { this->reports_fahrenheit_ = true; } @@ -43,6 +58,12 @@ class TuyaClimate : public climate::Climate, public Component { /// Override control to change settings of the climate device. void control(const climate::ClimateCall &call) override; + /// Override control to change settings of swing mode. + void control_swing_mode_(const climate::ClimateCall &call); + + /// Override control to change settings of fan mode. + void control_fan_mode_(const climate::ClimateCall &call); + /// Return the traits of this controller. climate::ClimateTraits traits() override; @@ -55,6 +76,12 @@ class TuyaClimate : public climate::Climate, public Component { /// Re-compute the state of this climate controller. void compute_state_(); + /// Re-Compute the swing mode of this climate controller. + void compute_swingmode_(); + + /// Re-Compute the fan mode of this climate controller. + void compute_fanmode_(); + /// Switch the climate device to the given climate mode. void switch_to_action_(climate::ClimateAction action); @@ -65,6 +92,8 @@ class TuyaClimate : public climate::Climate, public Component { optional active_state_id_{}; optional active_state_heating_value_{}; optional active_state_cooling_value_{}; + optional active_state_drying_value_{}; + optional active_state_fanonly_value_{}; GPIOPin *heating_state_pin_{nullptr}; GPIOPin *cooling_state_pin_{nullptr}; optional target_temperature_id_{}; @@ -73,12 +102,25 @@ class TuyaClimate : public climate::Climate, public Component { float target_temperature_multiplier_{1.0f}; float hysteresis_{1.0f}; optional eco_id_{}; + optional sleep_id_{}; optional eco_temperature_{}; uint8_t active_state_; + uint8_t fan_state_; + optional swing_vertical_id_{}; + optional swing_horizontal_id_{}; + optional fan_speed_id_{}; + optional fan_speed_low_value_{}; + optional fan_speed_medium_value_{}; + optional fan_speed_middle_value_{}; + optional fan_speed_high_value_{}; + optional fan_speed_auto_value_{}; + bool swing_vertical_{false}; + bool swing_horizontal_{false}; bool heating_state_{false}; bool cooling_state_{false}; float manual_temperature_; bool eco_; + bool sleep_; bool reports_fahrenheit_{false}; }; diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index 481c931f2e..8a613d0bae 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -34,9 +34,13 @@ void TuyaFan::setup() { } if (this->oscillation_id_.has_value()) { this->parent_->register_listener(*this->oscillation_id_, [this](const TuyaDatapoint &datapoint) { + // Whether data type is BOOL or ENUM, it will still be a 1 or a 0, so the functions below are valid in both + // scenarios ESP_LOGV(TAG, "MCU reported oscillation is: %s", ONOFF(datapoint.value_bool)); this->oscillating = datapoint.value_bool; this->publish_state(); + + this->oscillation_type_ = datapoint.type; }); } if (this->direction_id_.has_value()) { @@ -80,7 +84,11 @@ void TuyaFan::control(const fan::FanCall &call) { this->parent_->set_boolean_datapoint_value(*this->switch_id_, *call.get_state()); } if (this->oscillation_id_.has_value() && call.get_oscillating().has_value()) { - this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating()); + if (this->oscillation_type_ == TuyaDatapointType::ENUM) { + this->parent_->set_enum_datapoint_value(*this->oscillation_id_, *call.get_oscillating()); + } else if (this->speed_type_ == TuyaDatapointType::BOOLEAN) { + this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating()); + } } if (this->direction_id_.has_value() && call.get_direction().has_value()) { bool enable = *call.get_direction() == fan::FanDirection::REVERSE; diff --git a/esphome/components/tuya/fan/tuya_fan.h b/esphome/components/tuya/fan/tuya_fan.h index 77b2cc6383..527efa8246 100644 --- a/esphome/components/tuya/fan/tuya_fan.h +++ b/esphome/components/tuya/fan/tuya_fan.h @@ -29,6 +29,7 @@ class TuyaFan : public Component, public fan::Fan { optional direction_id_{}; int speed_count_{}; TuyaDatapointType speed_type_{}; + TuyaDatapointType oscillation_type_{}; }; } // namespace tuya diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index da03e3faad..1cc9681d09 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -23,8 +23,8 @@ static const int MAX_RETRIES = 5; void Tuya::setup() { this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); - if (this->status_pin_.has_value()) { - this->status_pin_.value()->digital_write(false); + if (this->status_pin_ != nullptr) { + this->status_pin_->digital_write(false); } } @@ -70,9 +70,7 @@ void Tuya::dump_config() { ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d", this->status_pin_reported_, this->reset_pin_reported_); } - if (this->status_pin_.has_value()) { - LOG_PIN(" Status Pin: ", this->status_pin_.value()); - } + LOG_PIN(" Status Pin: ", this->status_pin_); ESP_LOGCONFIG(TAG, " Product: '%s'", this->product_.c_str()); } @@ -194,7 +192,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff this->init_state_ = TuyaInitState::INIT_DATAPOINT; this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY); bool is_pin_equals = - this->status_pin_.has_value() && this->status_pin_.value()->get_pin() == this->status_pin_reported_; + this->status_pin_ != nullptr && this->status_pin_->get_pin() == this->status_pin_reported_; // Configure status pin toggling (if reported and configured) or WIFI_STATE periodic send if (is_pin_equals) { ESP_LOGV(TAG, "Configured status pin %i", this->status_pin_reported_); @@ -244,13 +242,12 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff break; case TuyaCommandType::LOCAL_TIME_QUERY: #ifdef USE_TIME - if (this->time_id_.has_value()) { + if (this->time_id_ != nullptr) { this->send_local_time_(); if (!this->time_sync_callback_registered_) { // tuya mcu supports time, so we let them know when our time changed - auto *time_id = *this->time_id_; - time_id->add_on_time_sync_callback([this] { this->send_local_time_(); }); + this->time_id_->add_on_time_sync_callback([this] { this->send_local_time_(); }); this->time_sync_callback_registered_ = true; } } else @@ -463,7 +460,7 @@ void Tuya::send_empty_command_(TuyaCommandType command) { void Tuya::set_status_pin_() { bool is_network_ready = network::is_connected() && remote_is_connected(); - this->status_pin_.value()->digital_write(is_network_ready); + this->status_pin_->digital_write(is_network_ready); } uint8_t Tuya::get_wifi_status_code_() { @@ -511,8 +508,7 @@ void Tuya::send_wifi_status_() { #ifdef USE_TIME void Tuya::send_local_time_() { std::vector payload; - auto *time_id = *this->time_id_; - ESPTime now = time_id->now(); + ESPTime now = this->time_id_->now(); if (now.is_valid()) { uint8_t year = now.year - 2000; uint8_t month = now.month; diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 27a97c3dc9..7dc405e3dd 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -130,14 +130,14 @@ class Tuya : public Component, public uart::UARTDevice { #ifdef USE_TIME void send_local_time_(); - optional time_id_{}; + time::RealTimeClock *time_id_{nullptr}; bool time_sync_callback_registered_{false}; #endif TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT; bool init_failed_{false}; int init_retries_{0}; uint8_t protocol_version_ = -1; - optional status_pin_{}; + InternalGPIOPin *status_pin_{nullptr}; int status_pin_reported_ = -1; int reset_pin_reported_ = -1; uint32_t last_command_timestamp_ = 0; diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index 6f27f36bcb..a57910c1a1 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -122,7 +122,7 @@ class UARTComponent { // @return Baud rate in bits per second. uint32_t get_baud_rate() const { return baud_rate_; } -#ifdef USE_ESP32 +#if defined(USE_ESP8266) || defined(USE_ESP32) /** * Load the UART settings. * @param dump_config If true (default), output the new settings to logs; otherwise, change settings quietly. @@ -147,7 +147,7 @@ class UARTComponent { * This will load the current UART interface with the latest settings (baud_rate, parity, etc). */ virtual void load_settings(){}; -#endif // USE_ESP32 +#endif // USE_ESP8266 || USE_ESP32 #ifdef USE_UART_DEBUGGER void add_debug_callback(std::function &&callback) { diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 529108f439..fa8dc3fb17 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -98,10 +98,26 @@ void ESP8266UartComponent::setup() { } } +void ESP8266UartComponent::load_settings(bool dump_config) { + ESP_LOGCONFIG(TAG, "Loading UART bus settings..."); + if (this->hw_serial_ != nullptr) { + SerialConfig config = static_cast(get_config()); + this->hw_serial_->begin(this->baud_rate_, config); + this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); + } else { + this->sw_serial_->setup(this->tx_pin_, this->rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_, + this->parity_, this->rx_buffer_size_); + } + if (dump_config) { + ESP_LOGCONFIG(TAG, "UART bus was reloaded."); + this->dump_config(); + } +} + void ESP8266UartComponent::dump_config() { ESP_LOGCONFIG(TAG, "UART Bus:"); - LOG_PIN(" TX Pin: ", tx_pin_); - LOG_PIN(" RX Pin: ", rx_pin_); + LOG_PIN(" TX Pin: ", this->tx_pin_); + LOG_PIN(" RX Pin: ", this->rx_pin_); if (this->rx_pin_ != nullptr) { ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); // NOLINT } diff --git a/esphome/components/uart/uart_component_esp8266.h b/esphome/components/uart/uart_component_esp8266.h index eed14f3265..749dd4c61e 100644 --- a/esphome/components/uart/uart_component_esp8266.h +++ b/esphome/components/uart/uart_component_esp8266.h @@ -63,6 +63,21 @@ class ESP8266UartComponent : public UARTComponent, public Component { uint32_t get_config(); + /** + * Load the UART with the current settings. + * @param dump_config (Optional, default `true`): True for displaying new settings or + * false to change it quitely + * + * Example: + * ```cpp + * id(uart1).load_settings(); + * ``` + * + * This will load the current UART interface with the latest settings (baud_rate, parity, etc). + */ + void load_settings(bool dump_config) override; + void load_settings() override { this->load_settings(true); } + protected: void check_logger_conflict() override; diff --git a/esphome/components/uponor_smatrix/__init__.py b/esphome/components/uponor_smatrix/__init__.py new file mode 100644 index 0000000000..35c4c4cecd --- /dev/null +++ b/esphome/components/uponor_smatrix/__init__.py @@ -0,0 +1,78 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart, time +from esphome.const import ( + CONF_ADDRESS, + CONF_ID, + CONF_TIME_ID, +) + +CODEOWNERS = ["@kroimon"] + +DEPENDENCIES = ["uart"] + +MULTI_CONF = True + +uponor_smatrix_ns = cg.esphome_ns.namespace("uponor_smatrix") +UponorSmatrixComponent = uponor_smatrix_ns.class_( + "UponorSmatrixComponent", cg.Component, uart.UARTDevice +) +UponorSmatrixDevice = uponor_smatrix_ns.class_( + "UponorSmatrixDevice", cg.Parented.template(UponorSmatrixComponent) +) + +CONF_UPONOR_SMATRIX_ID = "uponor_smatrix_id" +CONF_TIME_DEVICE_ADDRESS = "time_device_address" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(UponorSmatrixComponent), + cv.Optional(CONF_ADDRESS): cv.hex_uint16_t, + cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + cv.Optional(CONF_TIME_DEVICE_ADDRESS): cv.hex_uint16_t, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "uponor_smatrix", + baud_rate=19200, + require_tx=True, + require_rx=True, + data_bits=8, + parity=None, + stop_bits=1, +) + +# A schema to use for all Uponor Smatrix devices +UPONOR_SMATRIX_DEVICE_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_UPONOR_SMATRIX_ID): cv.use_id(UponorSmatrixComponent), + cv.Required(CONF_ADDRESS): cv.hex_uint16_t, + } +) + + +async def to_code(config): + cg.add_global(uponor_smatrix_ns.using) + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + if address := config.get(CONF_ADDRESS): + cg.add(var.set_system_address(address)) + if time_id := config.get(CONF_TIME_ID): + time_ = await cg.get_variable(time_id) + cg.add(var.set_time_id(time_)) + if time_device_address := config.get(CONF_TIME_DEVICE_ADDRESS): + cg.add(var.set_time_device_address(time_device_address)) + + +async def register_uponor_smatrix_device(var, config): + parent = await cg.get_variable(config[CONF_UPONOR_SMATRIX_ID]) + cg.add(var.set_parent(parent)) + cg.add(var.set_device_address(config[CONF_ADDRESS])) + cg.add(parent.register_device(var)) diff --git a/esphome/components/uponor_smatrix/climate/__init__.py b/esphome/components/uponor_smatrix/climate/__init__.py new file mode 100644 index 0000000000..0becec2624 --- /dev/null +++ b/esphome/components/uponor_smatrix/climate/__init__.py @@ -0,0 +1,33 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate +from esphome.const import CONF_ID + +from .. import ( + uponor_smatrix_ns, + UponorSmatrixDevice, + UPONOR_SMATRIX_DEVICE_SCHEMA, + register_uponor_smatrix_device, +) + +DEPENDENCIES = ["uponor_smatrix"] + +UponorSmatrixClimate = uponor_smatrix_ns.class_( + "UponorSmatrixClimate", + climate.Climate, + cg.Component, + UponorSmatrixDevice, +) + +CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(UponorSmatrixClimate), + } +).extend(UPONOR_SMATRIX_DEVICE_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await climate.register_climate(var, config) + await register_uponor_smatrix_device(var, config) diff --git a/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp b/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp new file mode 100644 index 0000000000..5afc628db3 --- /dev/null +++ b/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp @@ -0,0 +1,101 @@ +#include "uponor_smatrix_climate.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uponor_smatrix { + +static const char *const TAG = "uponor_smatrix.climate"; + +void UponorSmatrixClimate::dump_config() { + LOG_CLIMATE("", "Uponor Smatrix Climate", this); + ESP_LOGCONFIG(TAG, " Device address: 0x%04X", this->address_); +} + +void UponorSmatrixClimate::loop() { + const uint32_t now = millis(); + + // Publish state after all update packets are processed + if (this->last_data_ != 0 && (now - this->last_data_ > 100) && this->target_temperature_raw_ != 0) { + float temp = raw_to_celsius((this->preset == climate::CLIMATE_PRESET_ECO) + ? (this->target_temperature_raw_ - this->eco_setback_value_raw_) + : this->target_temperature_raw_); + float step = this->get_traits().get_visual_target_temperature_step(); + this->target_temperature = roundf(temp / step) * step; + this->publish_state(); + this->last_data_ = 0; + } +} + +climate::ClimateTraits UponorSmatrixClimate::traits() { + auto traits = climate::ClimateTraits(); + traits.set_supports_current_temperature(true); + traits.set_supports_current_humidity(true); + traits.set_supported_modes({climate::CLIMATE_MODE_HEAT}); + traits.set_supports_action(true); + traits.set_supported_presets({climate::CLIMATE_PRESET_ECO}); + traits.set_visual_min_temperature(this->min_temperature_); + traits.set_visual_max_temperature(this->max_temperature_); + traits.set_visual_current_temperature_step(0.1f); + traits.set_visual_target_temperature_step(0.5f); + return traits; +} + +void UponorSmatrixClimate::control(const climate::ClimateCall &call) { + if (call.get_target_temperature().has_value()) { + uint16_t temp = celsius_to_raw(*call.get_target_temperature()); + if (this->preset == climate::CLIMATE_PRESET_ECO) { + // During ECO mode, the thermostat automatically substracts the setback value from the setpoint, + // so we need to add it here first + temp += this->eco_setback_value_raw_; + } + + // For unknown reasons, we need to send a null setpoint first for the thermostat to react + UponorSmatrixData data[] = {{UPONOR_ID_TARGET_TEMP, 0}, {UPONOR_ID_TARGET_TEMP, temp}}; + this->send(data, sizeof(data) / sizeof(data[0])); + } +} + +void UponorSmatrixClimate::on_device_data(const UponorSmatrixData *data, size_t data_len) { + for (int i = 0; i < data_len; i++) { + switch (data[i].id) { + case UPONOR_ID_TARGET_TEMP_MIN: + this->min_temperature_ = raw_to_celsius(data[i].value); + break; + case UPONOR_ID_TARGET_TEMP_MAX: + this->max_temperature_ = raw_to_celsius(data[i].value); + break; + case UPONOR_ID_TARGET_TEMP: + // Ignore invalid values here as they are used by the controller to explicitely request the setpoint from a + // thermostat + if (data[i].value != UPONOR_INVALID_VALUE) + this->target_temperature_raw_ = data[i].value; + break; + case UPONOR_ID_ECO_SETBACK: + this->eco_setback_value_raw_ = data[i].value; + break; + case UPONOR_ID_DEMAND: + if (data[i].value & 0x1000) { + this->mode = climate::CLIMATE_MODE_COOL; + this->action = (data[i].value & 0x0040) ? climate::CLIMATE_ACTION_COOLING : climate::CLIMATE_ACTION_IDLE; + } else { + this->mode = climate::CLIMATE_MODE_HEAT; + this->action = (data[i].value & 0x0040) ? climate::CLIMATE_ACTION_HEATING : climate::CLIMATE_ACTION_IDLE; + } + break; + case UPONOR_ID_MODE1: + this->set_preset_((data[i].value & 0x0008) ? climate::CLIMATE_PRESET_ECO : climate::CLIMATE_PRESET_NONE); + break; + case UPONOR_ID_ROOM_TEMP: + this->current_temperature = raw_to_celsius(data[i].value); + break; + case UPONOR_ID_HUMIDITY: + this->current_humidity = data[i].value & 0x00FF; + } + } + + this->last_data_ = millis(); +} + +} // namespace uponor_smatrix +} // namespace esphome diff --git a/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.h b/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.h new file mode 100644 index 0000000000..b8458045c6 --- /dev/null +++ b/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/components/climate/climate.h" +#include "esphome/components/uponor_smatrix/uponor_smatrix.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace uponor_smatrix { + +class UponorSmatrixClimate : public climate::Climate, public Component, public UponorSmatrixDevice { + public: + void dump_config() override; + void loop() override; + + protected: + climate::ClimateTraits traits() override; + void control(const climate::ClimateCall &call) override; + void on_device_data(const UponorSmatrixData *data, size_t data_len) override; + + uint32_t last_data_; + float min_temperature_{5.0f}; + float max_temperature_{35.0f}; + uint16_t eco_setback_value_raw_{0x0048}; + uint16_t target_temperature_raw_; +}; + +} // namespace uponor_smatrix +} // namespace esphome diff --git a/esphome/components/uponor_smatrix/sensor/__init__.py b/esphome/components/uponor_smatrix/sensor/__init__.py new file mode 100644 index 0000000000..89097aef18 --- /dev/null +++ b/esphome/components/uponor_smatrix/sensor/__init__.py @@ -0,0 +1,70 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_EXTERNAL_TEMPERATURE, + CONF_HUMIDITY, + CONF_TEMPERATURE, + CONF_ID, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, +) + +from .. import ( + uponor_smatrix_ns, + UponorSmatrixDevice, + UPONOR_SMATRIX_DEVICE_SCHEMA, + register_uponor_smatrix_device, +) + +DEPENDENCIES = ["uponor_smatrix"] + +UponorSmatrixSensor = uponor_smatrix_ns.class_( + "UponorSmatrixSensor", + sensor.Sensor, + cg.Component, + UponorSmatrixDevice, +) + +CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(UponorSmatrixSensor), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } +).extend(UPONOR_SMATRIX_DEVICE_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await register_uponor_smatrix_device(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE): + sens = await sensor.new_sensor(external_temperature_config) + cg.add(var.set_external_temperature_sensor(sens)) + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity_sensor(sens)) diff --git a/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp b/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp new file mode 100644 index 0000000000..2fd2a36efc --- /dev/null +++ b/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp @@ -0,0 +1,37 @@ +#include "uponor_smatrix_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uponor_smatrix { + +static const char *const TAG = "uponor_smatrix.sensor"; + +void UponorSmatrixSensor::dump_config() { + ESP_LOGCONFIG(TAG, "Uponor Smatrix Sensor"); + ESP_LOGCONFIG(TAG, " Device address: 0x%04X", this->address_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "External Temperature", this->external_temperature_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); +} + +void UponorSmatrixSensor::on_device_data(const UponorSmatrixData *data, size_t data_len) { + for (int i = 0; i < data_len; i++) { + switch (data[i].id) { + case UPONOR_ID_ROOM_TEMP: + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(raw_to_celsius(data[i].value)); + break; + case UPONOR_ID_EXTERNAL_TEMP: + if (this->external_temperature_sensor_ != nullptr) + this->external_temperature_sensor_->publish_state(raw_to_celsius(data[i].value)); + break; + case UPONOR_ID_HUMIDITY: + if (this->humidity_sensor_ != nullptr) + this->humidity_sensor_->publish_state(data[i].value & 0x00FF); + break; + } + } +} + +} // namespace uponor_smatrix +} // namespace esphome diff --git a/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.h b/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.h new file mode 100644 index 0000000000..5e38117a21 --- /dev/null +++ b/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uponor_smatrix/uponor_smatrix.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace uponor_smatrix { + +class UponorSmatrixSensor : public sensor::Sensor, public Component, public UponorSmatrixDevice { + SUB_SENSOR(temperature) + SUB_SENSOR(external_temperature) + SUB_SENSOR(humidity) + + public: + void dump_config() override; + + protected: + void on_device_data(const UponorSmatrixData *data, size_t data_len) override; +}; + +} // namespace uponor_smatrix +} // namespace esphome diff --git a/esphome/components/uponor_smatrix/uponor_smatrix.cpp b/esphome/components/uponor_smatrix/uponor_smatrix.cpp new file mode 100644 index 0000000000..a7014dc96c --- /dev/null +++ b/esphome/components/uponor_smatrix/uponor_smatrix.cpp @@ -0,0 +1,229 @@ +#include "uponor_smatrix.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uponor_smatrix { + +static const char *const TAG = "uponor_smatrix"; + +void UponorSmatrixComponent::setup() { +#ifdef USE_TIME + if (this->time_id_ != nullptr) { + this->time_id_->add_on_time_sync_callback([this] { this->send_time(); }); + } +#endif +} + +void UponorSmatrixComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Uponor Smatrix"); + ESP_LOGCONFIG(TAG, " System address: 0x%04X", this->address_); +#ifdef USE_TIME + if (this->time_id_ != nullptr) { + ESP_LOGCONFIG(TAG, " Time synchronization: YES"); + ESP_LOGCONFIG(TAG, " Time master device address: 0x%04X", this->time_device_address_); + } +#endif + + this->check_uart_settings(19200); + + if (!this->unknown_devices_.empty()) { + ESP_LOGCONFIG(TAG, " Detected unknown device addresses:"); + for (auto device_address : this->unknown_devices_) { + ESP_LOGCONFIG(TAG, " 0x%04X", device_address); + } + } +} + +void UponorSmatrixComponent::loop() { + const uint32_t now = millis(); + + // Discard stale data + if (!this->rx_buffer_.empty() && (now - this->last_rx_ > 50)) { + ESP_LOGD(TAG, "Discarding %d bytes of unparsed data", this->rx_buffer_.size()); + this->rx_buffer_.clear(); + } + + // Read incoming data + while (this->available()) { + // The controller polls devices every 10 seconds, with around 200 ms between devices. + // Remember timestamps so we can send our own packets when the bus is expected to be silent. + if (now - this->last_rx_ > 500) { + this->last_poll_start_ = now; + } + this->last_rx_ = now; + + uint8_t byte; + this->read_byte(&byte); + if (this->parse_byte_(byte)) { + this->rx_buffer_.clear(); + } + } + + // Send packets during bus silence + if ((now - this->last_rx_ > 300) && (now - this->last_poll_start_ < 9500) && (now - this->last_tx_ > 200)) { +#ifdef USE_TIME + // Only build time packet when bus is silent and queue is empty to make sure we can send it right away + if (this->send_time_requested_ && this->tx_queue_.empty() && this->do_send_time_()) + this->send_time_requested_ = false; +#endif + // Send the next packet in the queue + if (!this->tx_queue_.empty()) { + auto packet = std::move(this->tx_queue_.front()); + this->tx_queue_.pop(); + + this->write_array(packet); + this->flush(); + + this->last_tx_ = now; + } + } +} + +bool UponorSmatrixComponent::parse_byte_(uint8_t byte) { + this->rx_buffer_.push_back(byte); + const uint8_t *packet = this->rx_buffer_.data(); + size_t packet_len = this->rx_buffer_.size(); + + if (packet_len < 7) { + // Minimum packet size is 7 bytes, wait for more + return false; + } + + uint16_t system_address = encode_uint16(packet[0], packet[1]); + uint16_t device_address = encode_uint16(packet[2], packet[3]); + uint16_t crc = encode_uint16(packet[packet_len - 1], packet[packet_len - 2]); + + uint16_t computed_crc = crc16(packet, packet_len - 2); + if (crc != computed_crc) { + // CRC did not match, more data might be coming + return false; + } + + ESP_LOGV(TAG, "Received packet: sys=%04X, dev=%04X, data=%s, crc=%04X", system_address, device_address, + format_hex(&packet[4], packet_len - 6).c_str(), crc); + + // Detect or check system address + if (this->address_ == 0) { + ESP_LOGI(TAG, "Using detected system address 0x%04X", system_address); + this->address_ = system_address; + } else if (this->address_ != system_address) { + // This should never happen except if the system address was set or detected incorrectly, so warn the user. + ESP_LOGW(TAG, "Received packet from unknown system address 0x%04X", system_address); + return true; + } + + // Handle packet + size_t data_len = (packet_len - 6) / 3; + if (data_len == 0) { + if (packet[4] == UPONOR_ID_REQUEST) + ESP_LOGVV(TAG, "Ignoring request packet for device 0x%04X", device_address); + return true; + } + + // Decode packet payload data for easy access + UponorSmatrixData data[data_len]; + for (int i = 0; i < data_len; i++) { + data[i].id = packet[(i * 3) + 4]; + data[i].value = encode_uint16(packet[(i * 3) + 5], packet[(i * 3) + 6]); + } + +#ifdef USE_TIME + // Detect device that acts as time master if not set explicitely + if (this->time_device_address_ == 0 && data_len >= 2) { + // The first thermostat paired to the controller will act as the time master. Time can only be manually adjusted at + // this first thermostat. To synchronize time, we need to know its address, so we search for packets coming from a + // thermostat sending both room temperature and time information. + bool found_temperature = false; + bool found_time = false; + for (int i = 0; i < data_len; i++) { + if (data[i].id == UPONOR_ID_ROOM_TEMP) + found_temperature = true; + if (data[i].id == UPONOR_ID_DATETIME1) + found_time = true; + if (found_temperature && found_time) { + ESP_LOGI(TAG, "Using detected time device address 0x%04X", device_address); + this->time_device_address_ = device_address; + break; + } + } + } +#endif + + // Forward data to device components + bool found = false; + for (auto *device : this->devices_) { + if (device->address_ == device_address) { + found = true; + device->on_device_data(data, data_len); + } + } + + // Log unknown device addresses + if (!found && !this->unknown_devices_.count(device_address)) { + ESP_LOGI(TAG, "Received packet for unknown device address 0x%04X ", device_address); + this->unknown_devices_.insert(device_address); + } + + // Return true to reset buffer + return true; +} + +bool UponorSmatrixComponent::send(uint16_t device_address, const UponorSmatrixData *data, size_t data_len) { + if (this->address_ == 0 || device_address == 0 || data == nullptr || data_len == 0) + return false; + + // Assemble packet for send queue. All fields are big-endian except for the little-endian checksum. + std::vector packet; + packet.reserve(6 + 3 * data_len); + + packet.push_back(this->address_ >> 8); + packet.push_back(this->address_ >> 0); + packet.push_back(device_address >> 8); + packet.push_back(device_address >> 0); + + for (int i = 0; i < data_len; i++) { + packet.push_back(data[i].id); + packet.push_back(data[i].value >> 8); + packet.push_back(data[i].value >> 0); + } + + auto crc = crc16(packet.data(), packet.size()); + packet.push_back(crc >> 0); + packet.push_back(crc >> 8); + + this->tx_queue_.push(packet); + return true; +} + +#ifdef USE_TIME +bool UponorSmatrixComponent::do_send_time_() { + if (this->time_device_address_ == 0 || this->time_id_ == nullptr) + return false; + + ESPTime now = this->time_id_->now(); + if (!now.is_valid()) + return false; + + uint8_t year = now.year - 2000; + uint8_t month = now.month; + // ESPHome days are [1-7] starting with Sunday, Uponor days are [0-6] starting with Monday + uint8_t day_of_week = (now.day_of_week == 1) ? 6 : (now.day_of_week - 2); + uint8_t day_of_month = now.day_of_month; + uint8_t hour = now.hour; + uint8_t minute = now.minute; + uint8_t second = now.second; + + uint16_t time1 = (year & 0x7F) << 7 | (month & 0x0F) << 3 | (day_of_week & 0x07); + uint16_t time2 = (day_of_month & 0x1F) << 11 | (hour & 0x1F) << 6 | (minute & 0x3F); + uint16_t time3 = second; + + ESP_LOGI(TAG, "Sending local time: %04d-%02d-%02d %02d:%02d:%02d", now.year, now.month, now.day_of_month, now.hour, + now.minute, now.second); + + UponorSmatrixData data[] = {{UPONOR_ID_DATETIME1, time1}, {UPONOR_ID_DATETIME2, time2}, {UPONOR_ID_DATETIME3, time3}}; + return this->send(this->time_device_address_, data, sizeof(data) / sizeof(data[0])); +} +#endif + +} // namespace uponor_smatrix +} // namespace esphome diff --git a/esphome/components/uponor_smatrix/uponor_smatrix.h b/esphome/components/uponor_smatrix/uponor_smatrix.h new file mode 100644 index 0000000000..b7667b5b87 --- /dev/null +++ b/esphome/components/uponor_smatrix/uponor_smatrix.h @@ -0,0 +1,130 @@ +#pragma once + +#include "esphome/components/uart/uart.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +#include "esphome/core/defines.h" + +#ifdef USE_TIME +#include "esphome/components/time/real_time_clock.h" +#include "esphome/core/time.h" +#endif + +#include +#include +#include + +namespace esphome { +namespace uponor_smatrix { + +/// Date/Time Part 1 (year, month, day of week) +static const uint8_t UPONOR_ID_DATETIME1 = 0x08; +/// Date/Time Part 2 (day of month, hour, minute) +static const uint8_t UPONOR_ID_DATETIME2 = 0x09; +/// Date/Time Part 3 (seconds) +static const uint8_t UPONOR_ID_DATETIME3 = 0x0A; +/// Unknown (observed values: 0x0342, 0x0024) +static const uint8_t UPONOR_ID_UNKNOWN1 = 0x0C; +/// Outdoor Temperature? (sent by controller) +static const uint8_t UPONOR_ID_OUTDOOR_TEMP = 0x2D; +/// Unknown (observed values: 0x8000) +static const uint8_t UPONOR_ID_UNKNOWN2 = 0x35; +/// Room Temperature Setpoint Minimum +static const uint8_t UPONOR_ID_TARGET_TEMP_MIN = 0x37; +/// Room Temperature Setpoint Maximum +static const uint8_t UPONOR_ID_TARGET_TEMP_MAX = 0x38; +/// Room Temperature Setpoint +static const uint8_t UPONOR_ID_TARGET_TEMP = 0x3B; +/// Room Temperature Setpoint Setback for ECO Mode +static const uint8_t UPONOR_ID_ECO_SETBACK = 0x3C; +/// Heating/Cooling Demand +static const uint8_t UPONOR_ID_DEMAND = 0x3D; +/// Thermostat Operating Mode 1 (ECO state, program schedule state) +static const uint8_t UPONOR_ID_MODE1 = 0x3E; +/// Thermostat Operating Mode 2 (sensor configuration, heating/cooling allowed) +static const uint8_t UPONOR_ID_MODE2 = 0x3F; +/// Current Room Temperature +static const uint8_t UPONOR_ID_ROOM_TEMP = 0x40; +/// Current External (Floor/Outdoor) Sensor Temperature +static const uint8_t UPONOR_ID_EXTERNAL_TEMP = 0x41; +/// Current Room Humidity +static const uint8_t UPONOR_ID_HUMIDITY = 0x42; +/// Data Request (sent by controller) +static const uint8_t UPONOR_ID_REQUEST = 0xFF; + +/// Indicating an invalid/missing value +static const uint16_t UPONOR_INVALID_VALUE = 0x7FFF; + +struct UponorSmatrixData { + uint8_t id; + uint16_t value; +}; + +class UponorSmatrixDevice; + +class UponorSmatrixComponent : public uart::UARTDevice, public Component { + public: + UponorSmatrixComponent() = default; + + void setup() override; + void dump_config() override; + void loop() override; + + void set_system_address(uint16_t address) { this->address_ = address; } + void register_device(UponorSmatrixDevice *device) { this->devices_.push_back(device); } + + bool send(uint16_t device_address, const UponorSmatrixData *data, size_t data_len); + +#ifdef USE_TIME + void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; } + void set_time_device_address(uint16_t address) { this->time_device_address_ = address; } + void send_time() { this->send_time_requested_ = true; } +#endif + + protected: + bool parse_byte_(uint8_t byte); + + uint16_t address_; + std::vector devices_; + std::set unknown_devices_; + + std::vector rx_buffer_; + std::queue> tx_queue_; + uint32_t last_rx_; + uint32_t last_tx_; + uint32_t last_poll_start_; + +#ifdef USE_TIME + time::RealTimeClock *time_id_{nullptr}; + uint16_t time_device_address_; + bool send_time_requested_; + bool do_send_time_(); +#endif +}; + +class UponorSmatrixDevice : public Parented { + public: + void set_device_address(uint16_t address) { this->address_ = address; } + + virtual void on_device_data(const UponorSmatrixData *data, size_t data_len) = 0; + bool send(const UponorSmatrixData *data, size_t data_len) { + return this->parent_->send(this->address_, data, data_len); + } + + protected: + friend UponorSmatrixComponent; + uint16_t address_; +}; + +inline float raw_to_celsius(uint16_t raw) { + return (raw == UPONOR_INVALID_VALUE) ? NAN : fahrenheit_to_celsius(raw / 10.0f); +} + +inline uint16_t celsius_to_raw(float celsius) { + return std::isnan(celsius) ? UPONOR_INVALID_VALUE + : static_cast(lroundf(celsius_to_fahrenheit(celsius) * 10.0f)); +} + +} // namespace uponor_smatrix +} // namespace esphome diff --git a/esphome/components/veml3235/__init__.py b/esphome/components/veml3235/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/veml3235/sensor.py b/esphome/components/veml3235/sensor.py new file mode 100644 index 0000000000..79ba510e41 --- /dev/null +++ b/esphome/components/veml3235/sensor.py @@ -0,0 +1,84 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_GAIN, + CONF_INTEGRATION_TIME, + DEVICE_CLASS_ILLUMINANCE, + STATE_CLASS_MEASUREMENT, + UNIT_LUX, +) + +CODEOWNERS = ["@kbx81"] +DEPENDENCIES = ["i2c"] + +CONF_AUTO_GAIN = "auto_gain" +CONF_AUTO_GAIN_THRESHOLD_HIGH = "auto_gain_threshold_high" +CONF_AUTO_GAIN_THRESHOLD_LOW = "auto_gain_threshold_low" +CONF_DIGITAL_GAIN = "digital_gain" + +veml3235_ns = cg.esphome_ns.namespace("veml3235") + +VEML3235Sensor = veml3235_ns.class_( + "VEML3235Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) +VEML3235IntegrationTime = veml3235_ns.enum("VEML3235IntegrationTime") +VEML3235_INTEGRATION_TIMES = { + "50ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_50MS, + "100ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_100MS, + "200ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_200MS, + "400ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_400MS, + "800ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_800MS, +} +VEML3235ComponentDigitalGain = veml3235_ns.enum("VEML3235ComponentDigitalGain") +DIGITAL_GAINS = { + "1X": VEML3235ComponentDigitalGain.VEML3235_DIGITAL_GAIN_1X, + "2X": VEML3235ComponentDigitalGain.VEML3235_DIGITAL_GAIN_2X, +} +VEML3235ComponentGain = veml3235_ns.enum("VEML3235ComponentGain") +GAINS = { + "1X": VEML3235ComponentGain.VEML3235_GAIN_1X, + "2X": VEML3235ComponentGain.VEML3235_GAIN_2X, + "4X": VEML3235ComponentGain.VEML3235_GAIN_4X, + "AUTO": VEML3235ComponentGain.VEML3235_GAIN_AUTO, +} + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + VEML3235Sensor, + unit_of_measurement=UNIT_LUX, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.Optional(CONF_DIGITAL_GAIN, default="1X"): cv.enum( + DIGITAL_GAINS, upper=True + ), + cv.Optional(CONF_AUTO_GAIN, default=True): cv.boolean, + cv.Optional(CONF_AUTO_GAIN_THRESHOLD_HIGH, default="90%"): cv.percentage, + cv.Optional(CONF_AUTO_GAIN_THRESHOLD_LOW, default="20%"): cv.percentage, + cv.Optional(CONF_GAIN, default="1X"): cv.enum(GAINS, upper=True), + cv.Optional(CONF_INTEGRATION_TIME, default="50ms"): cv.All( + cv.positive_time_period_milliseconds, + cv.enum(VEML3235_INTEGRATION_TIMES, lower=True), + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x10)) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + cg.add(var.set_auto_gain(config[CONF_AUTO_GAIN])) + cg.add(var.set_auto_gain_threshold_high(config[CONF_AUTO_GAIN_THRESHOLD_HIGH])) + cg.add(var.set_auto_gain_threshold_low(config[CONF_AUTO_GAIN_THRESHOLD_LOW])) + cg.add(var.set_digital_gain(DIGITAL_GAINS[config[CONF_DIGITAL_GAIN]])) + cg.add(var.set_gain(GAINS[config[CONF_GAIN]])) + cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) diff --git a/esphome/components/veml3235/veml3235.cpp b/esphome/components/veml3235/veml3235.cpp new file mode 100644 index 0000000000..2410bfdda2 --- /dev/null +++ b/esphome/components/veml3235/veml3235.cpp @@ -0,0 +1,230 @@ +#include "veml3235.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace veml3235 { + +static const char *const TAG = "veml3235.sensor"; + +void VEML3235Sensor::setup() { + uint8_t device_id[] = {0, 0}; + + ESP_LOGCONFIG(TAG, "Setting up VEML3235 '%s'...", this->name_.c_str()); + + if (!this->refresh_config_reg()) { + ESP_LOGE(TAG, "Unable to write configuration"); + this->mark_failed(); + return; + } + if ((this->write(&ID_REG, 1, false) != i2c::ERROR_OK) || !this->read_bytes_raw(device_id, 2)) { + ESP_LOGE(TAG, "Unable to read ID"); + this->mark_failed(); + return; + } else if (device_id[0] != DEVICE_ID) { + ESP_LOGE(TAG, "Incorrect device ID - expected 0x%.2x, read 0x%.2x", DEVICE_ID, device_id[0]); + this->mark_failed(); + return; + } +} + +bool VEML3235Sensor::refresh_config_reg(bool force_on) { + uint16_t data = this->power_on_ || force_on ? 0 : SHUTDOWN_BITS; + + data |= (uint16_t(this->integration_time_ << CONFIG_REG_IT_BIT)); + data |= (uint16_t(this->digital_gain_ << CONFIG_REG_DG_BIT)); + data |= (uint16_t(this->gain_ << CONFIG_REG_G_BIT)); + data |= 0x1; // mandatory 1 here per RM + + ESP_LOGVV(TAG, "Writing 0x%.4x to register 0x%.2x", data, CONFIG_REG); + return this->write_byte_16(CONFIG_REG, data); +} + +float VEML3235Sensor::read_lx_() { + if (!this->power_on_) { // if off, turn on + if (!this->refresh_config_reg(true)) { + ESP_LOGW(TAG, "Turning on failed"); + this->status_set_warning(); + return NAN; + } + delay(4); // from RM: a wait time of 4 ms should be observed before the first measurement is picked up, to allow + // for a correct start of the signal processor and oscillator + } + + uint8_t als_regs[] = {0, 0}; + if ((this->write(&ALS_REG, 1, false) != i2c::ERROR_OK) || !this->read_bytes_raw(als_regs, 2)) { + this->status_set_warning(); + return NAN; + } + + this->status_clear_warning(); + + float als_raw_value_multiplier = LUX_MULTIPLIER_BASE; + uint16_t als_raw_value = encode_uint16(als_regs[1], als_regs[0]); + // determine multiplier value based on gains and integration time + if (this->digital_gain_ == VEML3235_DIGITAL_GAIN_1X) { + als_raw_value_multiplier *= 2; + } + switch (this->gain_) { + case VEML3235_GAIN_1X: + als_raw_value_multiplier *= 4; + break; + case VEML3235_GAIN_2X: + als_raw_value_multiplier *= 2; + break; + default: + break; + } + switch (this->integration_time_) { + case VEML3235_INTEGRATION_TIME_50MS: + als_raw_value_multiplier *= 16; + break; + case VEML3235_INTEGRATION_TIME_100MS: + als_raw_value_multiplier *= 8; + break; + case VEML3235_INTEGRATION_TIME_200MS: + als_raw_value_multiplier *= 4; + break; + case VEML3235_INTEGRATION_TIME_400MS: + als_raw_value_multiplier *= 2; + break; + default: + break; + } + // finally, determine and return the actual lux value + float lx = float(als_raw_value) * als_raw_value_multiplier; + ESP_LOGVV(TAG, "'%s': ALS raw = %u, multiplier = %.5f", this->get_name().c_str(), als_raw_value, + als_raw_value_multiplier); + ESP_LOGD(TAG, "'%s': Illuminance = %.4flx", this->get_name().c_str(), lx); + + if (!this->power_on_) { // turn off if required + if (!this->refresh_config_reg()) { + ESP_LOGW(TAG, "Turning off failed"); + this->status_set_warning(); + } + } + + if (this->auto_gain_) { + this->adjust_gain_(als_raw_value); + } + + return lx; +} + +void VEML3235Sensor::adjust_gain_(const uint16_t als_raw_value) { + if ((als_raw_value > UINT16_MAX * this->auto_gain_threshold_low_) && + (als_raw_value < UINT16_MAX * this->auto_gain_threshold_high_)) { + return; + } + + if (als_raw_value >= UINT16_MAX * 0.9) { // over-saturated, reset all gains and start over + this->digital_gain_ = VEML3235_DIGITAL_GAIN_1X; + this->gain_ = VEML3235_GAIN_1X; + this->integration_time_ = VEML3235_INTEGRATION_TIME_50MS; + this->refresh_config_reg(); + return; + } + + if (this->gain_ != VEML3235_GAIN_4X) { // increase gain if possible + switch (this->gain_) { + case VEML3235_GAIN_1X: + this->gain_ = VEML3235_GAIN_2X; + break; + case VEML3235_GAIN_2X: + this->gain_ = VEML3235_GAIN_4X; + break; + default: + break; + } + this->refresh_config_reg(); + return; + } + // gain is maxed out; reset it and try to increase digital gain + if (this->digital_gain_ != VEML3235_DIGITAL_GAIN_2X) { // increase digital gain if possible + this->digital_gain_ = VEML3235_DIGITAL_GAIN_2X; + this->gain_ = VEML3235_GAIN_1X; + this->refresh_config_reg(); + return; + } + // digital gain is maxed out; reset it and try to increase integration time + if (this->integration_time_ != VEML3235_INTEGRATION_TIME_800MS) { // increase integration time if possible + switch (this->integration_time_) { + case VEML3235_INTEGRATION_TIME_50MS: + this->integration_time_ = VEML3235_INTEGRATION_TIME_100MS; + break; + case VEML3235_INTEGRATION_TIME_100MS: + this->integration_time_ = VEML3235_INTEGRATION_TIME_200MS; + break; + case VEML3235_INTEGRATION_TIME_200MS: + this->integration_time_ = VEML3235_INTEGRATION_TIME_400MS; + break; + case VEML3235_INTEGRATION_TIME_400MS: + this->integration_time_ = VEML3235_INTEGRATION_TIME_800MS; + break; + default: + break; + } + this->digital_gain_ = VEML3235_DIGITAL_GAIN_1X; + this->gain_ = VEML3235_GAIN_1X; + this->refresh_config_reg(); + return; + } +} + +void VEML3235Sensor::dump_config() { + uint8_t digital_gain = 1; + uint8_t gain = 1; + uint16_t integration_time = 0; + + if (this->digital_gain_ == VEML3235_DIGITAL_GAIN_2X) { + digital_gain = 2; + } + switch (this->gain_) { + case VEML3235_GAIN_2X: + gain = 2; + break; + case VEML3235_GAIN_4X: + gain = 4; + break; + default: + break; + } + switch (this->integration_time_) { + case VEML3235_INTEGRATION_TIME_50MS: + integration_time = 50; + break; + case VEML3235_INTEGRATION_TIME_100MS: + integration_time = 100; + break; + case VEML3235_INTEGRATION_TIME_200MS: + integration_time = 200; + break; + case VEML3235_INTEGRATION_TIME_400MS: + integration_time = 400; + break; + case VEML3235_INTEGRATION_TIME_800MS: + integration_time = 800; + break; + default: + break; + } + + LOG_SENSOR("", "VEML3235", this); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication failed"); + } + LOG_UPDATE_INTERVAL(this); + ESP_LOGCONFIG(TAG, " Auto-gain enabled: %s", YESNO(this->auto_gain_)); + if (this->auto_gain_) { + ESP_LOGCONFIG(TAG, " Auto-gain upper threshold: %f%%", this->auto_gain_threshold_high_ * 100.0); + ESP_LOGCONFIG(TAG, " Auto-gain lower threshold: %f%%", this->auto_gain_threshold_low_ * 100.0); + ESP_LOGCONFIG(TAG, " Values below will be used as initial values only"); + } + ESP_LOGCONFIG(TAG, " Digital gain: %uX", digital_gain); + ESP_LOGCONFIG(TAG, " Gain: %uX", gain); + ESP_LOGCONFIG(TAG, " Integration time: %ums", integration_time); +} + +} // namespace veml3235 +} // namespace esphome diff --git a/esphome/components/veml3235/veml3235.h b/esphome/components/veml3235/veml3235.h new file mode 100644 index 0000000000..2b0d6b23ea --- /dev/null +++ b/esphome/components/veml3235/veml3235.h @@ -0,0 +1,109 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace veml3235 { + +// Register IDs/locations +// +static const uint8_t CONFIG_REG = 0x00; +static const uint8_t W_REG = 0x04; +static const uint8_t ALS_REG = 0x05; +static const uint8_t ID_REG = 0x09; + +// Bit offsets within CONFIG_REG +// +static const uint8_t CONFIG_REG_IT_BIT = 12; +static const uint8_t CONFIG_REG_DG_BIT = 5; +static const uint8_t CONFIG_REG_G_BIT = 3; + +// Other important constants +// +static const uint8_t DEVICE_ID = 0x35; +static const uint16_t SHUTDOWN_BITS = 0x0018; + +// Base multiplier value for lux computation +// +static const float LUX_MULTIPLIER_BASE = 0.00213; + +// Enum for conversion/integration time settings for the VEML3235. +// +// Specific values of the enum constants are register values taken from the VEML3235 datasheet. +// Longer times mean more accurate results, but will take more energy/more time. +// +enum VEML3235ComponentIntegrationTime { + VEML3235_INTEGRATION_TIME_50MS = 0b000, + VEML3235_INTEGRATION_TIME_100MS = 0b001, + VEML3235_INTEGRATION_TIME_200MS = 0b010, + VEML3235_INTEGRATION_TIME_400MS = 0b011, + VEML3235_INTEGRATION_TIME_800MS = 0b100, +}; + +// Enum for digital gain settings for the VEML3235. +// Higher values are better for low light situations, but can increase noise. +// +enum VEML3235ComponentDigitalGain { + VEML3235_DIGITAL_GAIN_1X = 0b0, + VEML3235_DIGITAL_GAIN_2X = 0b1, +}; + +// Enum for gain settings for the VEML3235. +// Higher values are better for low light situations, but can increase noise. +// +enum VEML3235ComponentGain { + VEML3235_GAIN_1X = 0b00, + VEML3235_GAIN_2X = 0b01, + VEML3235_GAIN_4X = 0b11, +}; + +class VEML3235Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + void update() override { this->publish_state(this->read_lx_()); } + float get_setup_priority() const override { return setup_priority::DATA; } + + // Used by ESPHome framework. Does NOT actually set the value on the device. + void set_auto_gain(bool auto_gain) { this->auto_gain_ = auto_gain; } + void set_auto_gain_threshold_high(float auto_gain_threshold_high) { + this->auto_gain_threshold_high_ = auto_gain_threshold_high; + } + void set_auto_gain_threshold_low(float auto_gain_threshold_low) { + this->auto_gain_threshold_low_ = auto_gain_threshold_low; + } + void set_power_on(bool power_on) { this->power_on_ = power_on; } + void set_digital_gain(VEML3235ComponentDigitalGain digital_gain) { this->digital_gain_ = digital_gain; } + void set_gain(VEML3235ComponentGain gain) { this->gain_ = gain; } + void set_integration_time(VEML3235ComponentIntegrationTime integration_time) { + this->integration_time_ = integration_time; + } + + bool auto_gain() { return this->auto_gain_; } + float auto_gain_threshold_high() { return this->auto_gain_threshold_high_; } + float auto_gain_threshold_low() { return this->auto_gain_threshold_low_; } + VEML3235ComponentDigitalGain digital_gain() { return this->digital_gain_; } + VEML3235ComponentGain gain() { return this->gain_; } + VEML3235ComponentIntegrationTime integration_time() { return this->integration_time_; } + + // Updates the configuration register on the device + bool refresh_config_reg(bool force_on = false); + + protected: + float read_lx_(); + void adjust_gain_(uint16_t als_raw_value); + + bool auto_gain_{true}; + bool power_on_{true}; + float auto_gain_threshold_high_{0.9}; + float auto_gain_threshold_low_{0.2}; + VEML3235ComponentDigitalGain digital_gain_{VEML3235_DIGITAL_GAIN_1X}; + VEML3235ComponentGain gain_{VEML3235_GAIN_1X}; + VEML3235ComponentIntegrationTime integration_time_{VEML3235_INTEGRATION_TIME_50MS}; +}; + +} // namespace veml3235 +} // namespace esphome diff --git a/esphome/components/veml7700/__init__.py b/esphome/components/veml7700/__init__.py new file mode 100644 index 0000000000..dd06cfffea --- /dev/null +++ b/esphome/components/veml7700/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@latonita"] diff --git a/esphome/components/veml7700/sensor.py b/esphome/components/veml7700/sensor.py new file mode 100644 index 0000000000..7ce05b47e4 --- /dev/null +++ b/esphome/components/veml7700/sensor.py @@ -0,0 +1,190 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ACTUAL_GAIN, + CONF_AUTO_MODE, + CONF_FULL_SPECTRUM, + CONF_GAIN, + CONF_GLASS_ATTENUATION_FACTOR, + CONF_ID, + CONF_INFRARED, + CONF_INTEGRATION_TIME, + CONF_NAME, + UNIT_LUX, + UNIT_MILLISECOND, + ICON_BRIGHTNESS_5, + ICON_BRIGHTNESS_6, + ICON_TIMER, + DEVICE_CLASS_ILLUMINANCE, + STATE_CLASS_MEASUREMENT, +) + +CODEOWNERS = ["@latonita"] +DEPENDENCIES = ["i2c"] + +UNIT_COUNTS = "#" +ICON_MULTIPLICATION = "mdi:multiplication" +ICON_BRIGHTNESS_7 = "mdi:brightness-7" + +CONF_ACTUAL_INTEGRATION_TIME = "actual_integration_time" +CONF_AMBIENT_LIGHT = "ambient_light" +CONF_AMBIENT_LIGHT_COUNTS = "ambient_light_counts" +CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts" +CONF_LUX_COMPENSATION = "lux_compensation" + +veml7700_ns = cg.esphome_ns.namespace("veml7700") + +VEML7700Component = veml7700_ns.class_( + "VEML7700Component", cg.PollingComponent, i2c.I2CDevice +) + +Gain = veml7700_ns.enum("Gain") +GAINS = { + "1/8X": Gain.X_1_8, + "1/4X": Gain.X_1_4, + "1X": Gain.X_1, + "2X": Gain.X_2, +} + +IntegrationTime = veml7700_ns.enum("IntegrationTime") +INTEGRATION_TIMES = { + 25: IntegrationTime.INTEGRATION_TIME_25MS, + 50: IntegrationTime.INTEGRATION_TIME_50MS, + 100: IntegrationTime.INTEGRATION_TIME_100MS, + 200: IntegrationTime.INTEGRATION_TIME_200MS, + 400: IntegrationTime.INTEGRATION_TIME_400MS, + 800: IntegrationTime.INTEGRATION_TIME_800MS, +} + + +def validate_integration_time(value): + value = cv.positive_time_period_milliseconds(value).total_milliseconds + return cv.enum(INTEGRATION_TIMES, int=True)(value) + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(VEML7700Component), + cv.Optional(CONF_AUTO_MODE, default=True): cv.boolean, + cv.Optional(CONF_GAIN, default="1/8X"): cv.enum(GAINS, upper=True), + cv.Optional( + CONF_INTEGRATION_TIME, default="100ms" + ): validate_integration_time, + cv.Optional(CONF_LUX_COMPENSATION, default=True): cv.boolean, + cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range( + min=1.0 + ), + cv.Optional(CONF_AMBIENT_LIGHT): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + icon=ICON_BRIGHTNESS_6, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_AMBIENT_LIGHT_COUNTS): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_COUNTS, + icon=ICON_BRIGHTNESS_6, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_FULL_SPECTRUM): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + icon=ICON_BRIGHTNESS_7, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_FULL_SPECTRUM_COUNTS): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_COUNTS, + icon=ICON_BRIGHTNESS_7, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_INFRARED): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_ACTUAL_GAIN): cv.maybe_simple_value( + sensor.sensor_schema( + icon=ICON_MULTIPLICATION, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_ACTUAL_INTEGRATION_TIME): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_MILLISECOND, + icon=ICON_TIMER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x10)), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if als_config := config.get(CONF_AMBIENT_LIGHT): + sens = await sensor.new_sensor(als_config) + cg.add(var.set_ambient_light_sensor(sens)) + + if als_cnt_config := config.get(CONF_AMBIENT_LIGHT_COUNTS): + sens = await sensor.new_sensor(als_cnt_config) + cg.add(var.set_ambient_light_counts_sensor(sens)) + + if full_spect_config := config.get(CONF_FULL_SPECTRUM): + sens = await sensor.new_sensor(full_spect_config) + cg.add(var.set_white_sensor(sens)) + + if full_spect_cnt_config := config.get(CONF_FULL_SPECTRUM_COUNTS): + sens = await sensor.new_sensor(full_spect_cnt_config) + cg.add(var.set_white_counts_sensor(sens)) + + if infrared_config := config.get(CONF_INFRARED): + sens = await sensor.new_sensor(infrared_config) + cg.add(var.set_infrared_sensor(sens)) + + if act_gain_config := config.get(CONF_ACTUAL_GAIN): + sens = await sensor.new_sensor(act_gain_config) + cg.add(var.set_actual_gain_sensor(sens)) + + if act_itime_config := config.get(CONF_ACTUAL_INTEGRATION_TIME): + sens = await sensor.new_sensor(act_itime_config) + cg.add(var.set_actual_integration_time_sensor(sens)) + + cg.add(var.set_enable_automatic_mode(config[CONF_AUTO_MODE])) + cg.add(var.set_enable_lux_compensation(config[CONF_LUX_COMPENSATION])) + cg.add(var.set_gain(config[CONF_GAIN])) + cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) + cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) diff --git a/esphome/components/veml7700/veml7700.cpp b/esphome/components/veml7700/veml7700.cpp new file mode 100644 index 0000000000..68550811a1 --- /dev/null +++ b/esphome/components/veml7700/veml7700.cpp @@ -0,0 +1,437 @@ +#include "veml7700.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace veml7700 { + +static const char *const TAG = "veml7700"; +static const size_t VEML_REG_SIZE = 2; + +static float reduce_to_zero(float a, float b) { return (a > b) ? (a - b) : 0; } + +template T get_next(const T (&array)[size], const T val) { + size_t i = 0; + size_t idx = -1; + while (idx == -1 && i < size) { + if (array[i] == val) { + idx = i; + break; + } + i++; + } + if (idx == -1 || i + 1 >= size) + return val; + return array[i + 1]; +} + +template T get_prev(const T (&array)[size], const T val) { + size_t i = size - 1; + size_t idx = -1; + while (idx == -1 && i > 0) { + if (array[i] == val) { + idx = i; + break; + } + i--; + } + if (idx == -1 || i == 0) + return val; + return array[i - 1]; +} + +static uint16_t get_itime_ms(IntegrationTime time) { + uint16_t ms = 0; + switch (time) { + case INTEGRATION_TIME_100MS: + ms = 100; + break; + case INTEGRATION_TIME_200MS: + ms = 200; + break; + case INTEGRATION_TIME_400MS: + ms = 400; + break; + case INTEGRATION_TIME_800MS: + ms = 800; + break; + case INTEGRATION_TIME_50MS: + ms = 50; + break; + case INTEGRATION_TIME_25MS: + ms = 25; + break; + default: + ms = 100; + } + return ms; +} + +static float get_gain_coeff(Gain gain) { + static const float GAIN_FLOAT[GAINS_COUNT] = {1.0f, 2.0f, 0.125f, 0.25f}; + return GAIN_FLOAT[gain & 0b11]; +} + +static const char *get_gain_str(Gain gain) { + static const char *gain_str[GAINS_COUNT] = {"1x", "2x", "1/8x", "1/4x"}; + return gain_str[gain & 0b11]; +} + +void VEML7700Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up VEML7700/6030..."); + + auto err = this->configure_(); + if (err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Sensor configuration failed"); + this->mark_failed(); + } else { + this->state_ = State::INITIAL_SETUP_COMPLETED; + } +} + +void VEML7700Component::dump_config() { + LOG_I2C_DEVICE(this); + ESP_LOGCONFIG(TAG, " Automatic gain/time: %s", YESNO(this->automatic_mode_enabled_)); + if (!this->automatic_mode_enabled_) { + ESP_LOGCONFIG(TAG, " Gain: %s", get_gain_str(this->gain_)); + ESP_LOGCONFIG(TAG, " Integration time: %d ms", get_itime_ms(this->integration_time_)); + } + ESP_LOGCONFIG(TAG, " Lux compensation: %s", YESNO(this->lux_compensation_enabled_)); + ESP_LOGCONFIG(TAG, " Glass attenuation factor: %f", this->glass_attenuation_factor_); + LOG_UPDATE_INTERVAL(this); + + LOG_SENSOR(" ", "ALS channel lux", this->ambient_light_sensor_); + LOG_SENSOR(" ", "ALS channel counts", this->ambient_light_counts_sensor_); + LOG_SENSOR(" ", "WHITE channel lux", this->white_sensor_); + LOG_SENSOR(" ", "WHITE channel counts", this->white_counts_sensor_); + LOG_SENSOR(" ", "FAKE_IR channel lux", this->fake_infrared_sensor_); + LOG_SENSOR(" ", "Actual gain", this->actual_gain_sensor_); + LOG_SENSOR(" ", "Actual integration time", this->actual_integration_time_sensor_); + + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with I2C VEML7700/6030 failed!"); + } +} + +void VEML7700Component::update() { + if (this->is_ready() && this->state_ == State::IDLE) { + ESP_LOGV(TAG, "Update: Initiating new data collection"); + + this->state_ = this->automatic_mode_enabled_ ? State::COLLECTING_DATA_AUTO : State::COLLECTING_DATA; + + this->readings_.als_counts = 0; + this->readings_.white_counts = 0; + this->readings_.actual_time = this->integration_time_; + this->readings_.actual_gain = this->gain_; + this->readings_.als_lux = 0; + this->readings_.white_lux = 0; + this->readings_.fake_infrared_lux = 0; + } else { + ESP_LOGV(TAG, "Update: Component not ready yet"); + } +} + +void VEML7700Component::loop() { + ErrorCode err = i2c::ERROR_OK; + + if (this->state_ == State::INITIAL_SETUP_COMPLETED) { + // Datasheet: 2.5 ms before the first measurement is needed, allowing for the correct start of the signal processor + // and oscillator. + // Reality: wait for couple integration times to have first samples captured + this->set_timeout(2 * this->integration_time_, [this]() { this->state_ = State::IDLE; }); + } + + if (this->is_ready()) { + switch (this->state_) { + case State::IDLE: + // doing nothing, having best time + break; + + case State::COLLECTING_DATA: + err = this->read_sensor_output_(this->readings_); + this->state_ = (err == i2c::ERROR_OK) ? State::DATA_COLLECTED : State::IDLE; + break; + + case State::COLLECTING_DATA_AUTO: // Automatic mode - we start here to reconfigure device first + case State::DATA_COLLECTED: + if (!this->are_adjustments_required_(this->readings_)) { + this->state_ = State::READY_TO_PUBLISH_PART_1; + } else { + // if sensitivity adjustment needed - + // shutdown device to change config and wait one integration time period + this->state_ = State::ADJUSTMENT_IN_PROGRESS; + err = this->reconfigure_time_and_gain_(this->readings_.actual_time, this->readings_.actual_gain, true); + if (err == i2c::ERROR_OK) { + this->set_timeout(1 * get_itime_ms(this->readings_.actual_time), + [this]() { this->state_ = State::READY_TO_APPLY_ADJUSTMENTS; }); + } else { + this->state_ = State::IDLE; + } + } + break; + + case State::ADJUSTMENT_IN_PROGRESS: + // nothing to be done, just waiting for the timeout + break; + + case State::READY_TO_APPLY_ADJUSTMENTS: + // second stage of sensitivity adjustment - turn device back on + // and wait 2-3 integration time periods to get good data samples + this->state_ = State::ADJUSTMENT_IN_PROGRESS; + err = this->reconfigure_time_and_gain_(this->readings_.actual_time, this->readings_.actual_gain, false); + if (err == i2c::ERROR_OK) { + this->set_timeout(3 * get_itime_ms(this->readings_.actual_time), + [this]() { this->state_ = State::COLLECTING_DATA; }); + } else { + this->state_ = State::IDLE; + } + break; + + case State::READY_TO_PUBLISH_PART_1: + this->status_clear_warning(); + + this->apply_lux_calculation_(this->readings_); + this->apply_lux_compensation_(this->readings_); + this->apply_glass_attenuation_(this->readings_); + + this->publish_data_part_1_(this->readings_); + this->state_ = State::READY_TO_PUBLISH_PART_2; + break; + + case State::READY_TO_PUBLISH_PART_2: + this->publish_data_part_2_(this->readings_); + this->state_ = State::READY_TO_PUBLISH_PART_3; + break; + + case State::READY_TO_PUBLISH_PART_3: + this->publish_data_part_3_(this->readings_); + this->state_ = State::IDLE; + break; + + default: + break; + } + if (err != i2c::ERROR_OK) + this->status_set_warning(); + } +} + +ErrorCode VEML7700Component::configure_() { + ESP_LOGV(TAG, "Configure"); + + ConfigurationRegister als_conf{0}; + als_conf.ALS_INT_EN = false; + als_conf.ALS_PERS = Persistence::PERSISTENCE_1; + als_conf.ALS_IT = this->integration_time_; + als_conf.ALS_GAIN = this->gain_; + + als_conf.ALS_SD = true; + ESP_LOGV(TAG, "Shutdown before config. ALS_CONF_0 to 0x%04X", als_conf.raw); + auto err = this->write_register((uint8_t) CommandRegisters::ALS_CONF_0, als_conf.raw_bytes, VEML_REG_SIZE); + if (err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Failed to shutdown, I2C error %d", err); + return err; + } + delay(3); + + als_conf.ALS_SD = false; + ESP_LOGV(TAG, "Turning on. Setting ALS_CONF_0 to 0x%04X", als_conf.raw); + err = this->write_register((uint8_t) CommandRegisters::ALS_CONF_0, als_conf.raw_bytes, VEML_REG_SIZE); + if (err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Failed to turn on, I2C error %d", err); + return err; + } + + PSMRegister psm{0}; + psm.PSM = PSM::PSM_MODE_1; + psm.PSM_EN = false; + ESP_LOGV(TAG, "Setting PSM to 0x%04X", psm.raw); + err = this->write_register((uint8_t) CommandRegisters::PWR_SAVING, psm.raw_bytes, VEML_REG_SIZE); + if (err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Failed to set PSM, I2C error %d", err); + return err; + } + + return err; +} + +ErrorCode VEML7700Component::reconfigure_time_and_gain_(IntegrationTime time, Gain gain, bool shutdown) { + ESP_LOGV(TAG, "Reconfigure time and gain (%d ms, %s) %s", get_itime_ms(time), get_gain_str(gain), + shutdown ? "Shutting down" : "Turning back on"); + + ConfigurationRegister als_conf{0}; + als_conf.raw = 0; + + // We have to before changing parameters + als_conf.ALS_SD = shutdown; + als_conf.ALS_INT_EN = false; + als_conf.ALS_PERS = Persistence::PERSISTENCE_1; + als_conf.ALS_IT = time; + als_conf.ALS_GAIN = gain; + auto err = this->write_register((uint8_t) CommandRegisters::ALS_CONF_0, als_conf.raw_bytes, VEML_REG_SIZE); + if (err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "%s failed", shutdown ? "Shutdown" : "Turn on"); + } + + return err; +} + +ErrorCode VEML7700Component::read_sensor_output_(Readings &data) { + auto als_err = + this->read_register((uint8_t) CommandRegisters::ALS, (uint8_t *) &data.als_counts, VEML_REG_SIZE, false); + if (als_err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Error reading ALS register, err = %d", als_err); + } + auto white_err = + this->read_register((uint8_t) CommandRegisters::WHITE, (uint8_t *) &data.white_counts, VEML_REG_SIZE, false); + if (white_err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Error reading WHITE register, err = %d", white_err); + } + + ConfigurationRegister conf{0}; + auto err = + this->read_register((uint8_t) CommandRegisters::ALS_CONF_0, (uint8_t *) conf.raw_bytes, VEML_REG_SIZE, false); + if (err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Error reading ALS_CONF_0 register, err = %d", white_err); + } + data.actual_time = conf.ALS_IT; + data.actual_gain = conf.ALS_GAIN; + + ESP_LOGV(TAG, "Data from sensors: ALS = %d, WHITE = %d, Gain = %s, Time = %d ms", data.als_counts, data.white_counts, + get_gain_str(data.actual_gain), get_itime_ms(data.actual_time)); + return std::max(als_err, white_err); +} + +bool VEML7700Component::are_adjustments_required_(Readings &data) { + // skip first sample in auto mode - + // we need to reconfigure device after last measurement + if (this->state_ == State::COLLECTING_DATA_AUTO) + return true; + + if (!this->automatic_mode_enabled_) + return false; + + // Recommended thresholds as per datasheet + static constexpr uint16_t LOW_INTENSITY_THRESHOLD = 100; + static constexpr uint16_t HIGH_INTENSITY_THRESHOLD = 10000; + + static const IntegrationTime TIMES[INTEGRATION_TIMES_COUNT] = {INTEGRATION_TIME_25MS, INTEGRATION_TIME_50MS, + INTEGRATION_TIME_100MS, INTEGRATION_TIME_200MS, + INTEGRATION_TIME_400MS, INTEGRATION_TIME_800MS}; + static const Gain GAINS[GAINS_COUNT] = {X_1_8, X_1_4, X_1, X_2}; + + if (data.als_counts <= LOW_INTENSITY_THRESHOLD) { + Gain next_gain = get_next(GAINS, data.actual_gain); + if (next_gain != data.actual_gain) { + data.actual_gain = next_gain; + return true; + } + IntegrationTime next_time = get_next(TIMES, data.actual_time); + if (next_time != data.actual_time) { + data.actual_time = next_time; + return true; + } + } else if (data.als_counts >= HIGH_INTENSITY_THRESHOLD) { + Gain prev_gain = get_prev(GAINS, data.actual_gain); + if (prev_gain != data.actual_gain) { + data.actual_gain = prev_gain; + return true; + } + IntegrationTime prev_time = get_prev(TIMES, data.actual_time); + if (prev_time != data.actual_time) { + data.actual_time = prev_time; + return true; + } + } + + // Counts are either good (between thresholds) + // or there is no room to change sensitivity anymore + return false; +} + +void VEML7700Component::apply_lux_calculation_(Readings &data) { + static const float MAX_GAIN = 2.0f; + static const float MAX_ITIME_MS = 800.0f; + static const float MAX_LX_RESOLUTION = 0.0036f; + float lux_resolution = (MAX_ITIME_MS / (float) get_itime_ms(data.actual_time)) * + (MAX_GAIN / get_gain_coeff(data.actual_gain)) * MAX_LX_RESOLUTION; + ESP_LOGV(TAG, "Lux resolution for (%d, %s) = %.4f ", get_itime_ms(data.actual_time), get_gain_str(data.actual_gain), + lux_resolution); + + data.als_lux = lux_resolution * (float) data.als_counts; + data.white_lux = lux_resolution * (float) data.white_counts; + data.fake_infrared_lux = reduce_to_zero(data.white_lux, data.als_lux); + + ESP_LOGV(TAG, "%s mode - ALS = %.1f lx, WHITE = %.1f lx, FAKE_IR = %.1f lx", + this->automatic_mode_enabled_ ? "Automatic" : "Manual", data.als_lux, data.white_lux, + data.fake_infrared_lux); +} + +void VEML7700Component::apply_lux_compensation_(Readings &data) { + if (!this->lux_compensation_enabled_) + return; + auto &local_data = data; + // Always apply correction for G1/4 and G1/8 + // Other Gains G1 and G2 are not supposed to be used for lux > 1000, + // corrections may help, but not a lot. + // + // "Illumination values higher than 1000 lx show non-linearity. + // This non-linearity is the same for all sensors, so a compensation formula can be applied + // if this light level is exceeded" + auto compensate = [&local_data](float &lux) { + auto calculate_high_lux_compensation = [](float lux_veml) -> float { + return (((6.0135e-13 * lux_veml - 9.3924e-9) * lux_veml + 8.1488e-5) * lux_veml + 1.0023) * lux_veml; + }; + + if (lux > 1000.0f || local_data.actual_gain == Gain::X_1_8 || local_data.actual_gain == Gain::X_1_4) { + lux = calculate_high_lux_compensation(lux); + } + }; + + compensate(data.als_lux); + compensate(data.white_lux); + data.fake_infrared_lux = reduce_to_zero(data.white_lux, data.als_lux); + + ESP_LOGV(TAG, "Lux compensation - ALS = %.1f lx, WHITE = %.1f lx, FAKE_IR = %.1f lx", data.als_lux, data.white_lux, + data.fake_infrared_lux); +} + +void VEML7700Component::apply_glass_attenuation_(Readings &data) { + data.als_lux *= this->glass_attenuation_factor_; + data.white_lux *= this->glass_attenuation_factor_; + data.fake_infrared_lux = reduce_to_zero(data.white_lux, data.als_lux); + ESP_LOGV(TAG, "Glass attenuation - ALS = %.1f lx, WHITE = %.1f lx, FAKE_IR = %.1f lx", data.als_lux, data.white_lux, + data.fake_infrared_lux); +} + +void VEML7700Component::publish_data_part_1_(Readings &data) { + if (this->ambient_light_sensor_ != nullptr) { + this->ambient_light_sensor_->publish_state(data.als_lux); + } + if (this->white_sensor_ != nullptr) { + this->white_sensor_->publish_state(data.white_lux); + } +} + +void VEML7700Component::publish_data_part_2_(Readings &data) { + if (this->fake_infrared_sensor_ != nullptr) { + this->fake_infrared_sensor_->publish_state(data.fake_infrared_lux); + } + if (this->ambient_light_counts_sensor_ != nullptr) { + this->ambient_light_counts_sensor_->publish_state(data.als_counts); + } + if (this->white_counts_sensor_ != nullptr) { + this->white_counts_sensor_->publish_state(data.white_counts); + } +} + +void VEML7700Component::publish_data_part_3_(Readings &data) { + if (this->actual_gain_sensor_ != nullptr) { + this->actual_gain_sensor_->publish_state(get_gain_coeff(data.actual_gain)); + } + if (this->actual_integration_time_sensor_ != nullptr) { + this->actual_integration_time_sensor_->publish_state(get_itime_ms(data.actual_time)); + } +} +} // namespace veml7700 +} // namespace esphome diff --git a/esphome/components/veml7700/veml7700.h b/esphome/components/veml7700/veml7700.h new file mode 100644 index 0000000000..fe5e1158e3 --- /dev/null +++ b/esphome/components/veml7700/veml7700.h @@ -0,0 +1,202 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" +#include "esphome/core/optional.h" + +namespace esphome { +namespace veml7700 { + +using esphome::i2c::ErrorCode; + +// +// Datasheet: https://www.vishay.com/docs/84286/veml7700.pdf +// + +enum class CommandRegisters : uint8_t { + ALS_CONF_0 = 0x00, // W: ALS gain, integration time, interrupt, and shutdown + ALS_WH = 0x01, // W: ALS high threshold window setting + ALS_WL = 0x02, // W: ALS low threshold window setting + PWR_SAVING = 0x03, // W: Set (15 : 3) 0000 0000 0000 0b + ALS = 0x04, // R: MSB, LSB data of whole ALS 16 bits + WHITE = 0x05, // R: MSB, LSB data of whole WHITE 16 bits + ALS_INT = 0x06 // R: ALS INT trigger event +}; + +enum Gain : uint8_t { + X_1 = 0, + X_2 = 1, + X_1_8 = 2, + X_1_4 = 3, +}; +const uint8_t GAINS_COUNT = 4; + +enum IntegrationTime : uint8_t { + INTEGRATION_TIME_25MS = 0b1100, + INTEGRATION_TIME_50MS = 0b1000, + INTEGRATION_TIME_100MS = 0b0000, + INTEGRATION_TIME_200MS = 0b0001, + INTEGRATION_TIME_400MS = 0b0010, + INTEGRATION_TIME_800MS = 0b0011, +}; +const uint8_t INTEGRATION_TIMES_COUNT = 6; + +enum Persistence : uint8_t { + PERSISTENCE_1 = 0, + PERSISTENCE_2 = 1, + PERSISTENCE_4 = 2, + PERSISTENCE_8 = 3, +}; + +enum PSM : uint8_t { + PSM_MODE_1 = 0, + PSM_MODE_2 = 1, + PSM_MODE_3 = 2, + PSM_MODE_4 = 3, +}; + +// The following section with bit-fields brings GCC compilation 'notes' about padding bytes due to bug in older GCC back +// in 2009 "Packed bit-fields of type char were not properly bit-packed on many targets prior to GCC 4.4" Even more to +// this - this message can't be disabled with "#pragma GCC diagnostic ignored" due to another bug which was only fixed +// in GCC 13 in 2022 :) No actions required, it is just a note. The code is correct. + +// +// VEML7700_CR_ALS_CONF_0 Register (0x00) +// +union ConfigurationRegister { + uint16_t raw; + uint8_t raw_bytes[2]; + struct { + bool ALS_SD : 1; // ALS shut down setting: 0 = ALS power on, 1 = ALS shut + // down + bool ALS_INT_EN : 1; // ALS interrupt enable setting: 0 = ALS INT disable, 1 + // = ALS INT enable + bool reserved_2 : 1; // 0 + bool reserved_3 : 1; // 0 + Persistence ALS_PERS : 2; // 00 - 1, 01- 2, 10 - 4, 11 - 8 + IntegrationTime ALS_IT : 4; // ALS integration time setting + bool reserved_10 : 1; // 0 + Gain ALS_GAIN : 2; // Gain selection + bool reserved_13 : 1; // 0 + bool reserved_14 : 1; // 0 + bool reserved_15 : 1; // 0 + } __attribute__((packed)); +}; + +// +// Power Saving Mode: PSM Register (0x03) +// +union PSMRegister { + uint16_t raw; + uint8_t raw_bytes[2]; + struct { + bool PSM_EN : 1; + uint8_t PSM : 2; + uint16_t reserved : 13; + } __attribute__((packed)); +}; + +class VEML7700Component : public PollingComponent, public i2c::I2CDevice { + public: + // + // EspHome framework functions + // + float get_setup_priority() const override { return setup_priority::DATA; } + void setup() override; + void dump_config() override; + void update() override; + void loop() override; + + // + // Configuration setters + // + void set_gain(Gain gain) { this->gain_ = gain; } + void set_integration_time(IntegrationTime time) { this->integration_time_ = time; } + void set_enable_automatic_mode(bool enable) { this->automatic_mode_enabled_ = enable; } + void set_enable_lux_compensation(bool enable) { this->lux_compensation_enabled_ = enable; } + void set_glass_attenuation_factor(float factor) { this->glass_attenuation_factor_ = factor; } + + void set_ambient_light_sensor(sensor::Sensor *sensor) { this->ambient_light_sensor_ = sensor; } + void set_ambient_light_counts_sensor(sensor::Sensor *sensor) { this->ambient_light_counts_sensor_ = sensor; } + void set_white_sensor(sensor::Sensor *sensor) { this->white_sensor_ = sensor; } + void set_white_counts_sensor(sensor::Sensor *sensor) { this->white_counts_sensor_ = sensor; } + void set_infrared_sensor(sensor::Sensor *sensor) { this->fake_infrared_sensor_ = sensor; } + void set_actual_gain_sensor(sensor::Sensor *sensor) { this->actual_gain_sensor_ = sensor; } + void set_actual_integration_time_sensor(sensor::Sensor *sensor) { this->actual_integration_time_sensor_ = sensor; } + + protected: + // + // Internal state machine, used to split all the actions into + // small steps in loop() to make sure we are not blocking execution + // + enum class State : uint8_t { + NOT_INITIALIZED, + INITIAL_SETUP_COMPLETED, + IDLE, + COLLECTING_DATA, + COLLECTING_DATA_AUTO, + DATA_COLLECTED, + ADJUSTMENT_NEEDED, + ADJUSTMENT_IN_PROGRESS, + READY_TO_APPLY_ADJUSTMENTS, + READY_TO_PUBLISH_PART_1, + READY_TO_PUBLISH_PART_2, + READY_TO_PUBLISH_PART_3 + } state_{State::NOT_INITIALIZED}; + + // + // Current measurements data + // + struct Readings { + uint16_t als_counts{0}; + uint16_t white_counts{0}; + IntegrationTime actual_time{INTEGRATION_TIME_100MS}; + Gain actual_gain{X_1_8}; + float als_lux{0}; + float white_lux{0}; + float fake_infrared_lux{0}; + ErrorCode err{i2c::ERROR_OK}; + } readings_; + + // + // Device interaction + // + ErrorCode configure_(); + ErrorCode reconfigure_time_and_gain_(IntegrationTime time, Gain gain, bool shutdown); + ErrorCode read_sensor_output_(Readings &data); + + // + // Working with the data + // + bool are_adjustments_required_(Readings &data); + void apply_lux_calculation_(Readings &data); + void apply_lux_compensation_(Readings &data); + void apply_glass_attenuation_(Readings &data); + void publish_data_part_1_(Readings &data); + void publish_data_part_2_(Readings &data); + void publish_data_part_3_(Readings &data); + + // + // Component configuration + // + bool automatic_mode_enabled_{true}; + bool lux_compensation_enabled_{true}; + float glass_attenuation_factor_{1.0}; + IntegrationTime integration_time_{INTEGRATION_TIME_100MS}; + Gain gain_{X_1}; + + // + // Sensors for publishing data + // + sensor::Sensor *ambient_light_sensor_{nullptr}; // Human eye range 500-600 nm, lx + sensor::Sensor *ambient_light_counts_sensor_{nullptr}; // Raw counts + sensor::Sensor *white_sensor_{nullptr}; // Wide range 450-950 nm, lx + sensor::Sensor *white_counts_sensor_{nullptr}; // Raw counts + sensor::Sensor *fake_infrared_sensor_{nullptr}; // Artificial. = WHITE lx - ALS lx. + sensor::Sensor *actual_gain_sensor_{nullptr}; // Actual gain multiplier for the measurement + sensor::Sensor *actual_integration_time_sensor_{nullptr}; // Actual integration time for the measurement +}; + +} // namespace veml7700 +} // namespace esphome diff --git a/esphome/components/voice_assistant/__init__.py b/esphome/components/voice_assistant/__init__.py index 59aef901f2..17bdffd9da 100644 --- a/esphome/components/voice_assistant/__init__.py +++ b/esphome/components/voice_assistant/__init__.py @@ -32,6 +32,7 @@ CONF_ON_TTS_START = "on_tts_start" CONF_ON_TTS_STREAM_START = "on_tts_stream_start" CONF_ON_TTS_STREAM_END = "on_tts_stream_end" CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected" +CONF_ON_IDLE = "on_idle" CONF_SILENCE_DETECTION = "silence_detection" CONF_USE_WAKE_WORD = "use_wake_word" @@ -41,6 +42,8 @@ CONF_AUTO_GAIN = "auto_gain" CONF_NOISE_SUPPRESSION_LEVEL = "noise_suppression_level" CONF_VOLUME_MULTIPLIER = "volume_multiplier" +CONF_WAKE_WORD = "wake_word" + voice_assistant_ns = cg.esphome_ns.namespace("voice_assistant") VoiceAssistant = voice_assistant_ns.class_("VoiceAssistant", cg.Component) @@ -127,6 +130,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ON_TTS_STREAM_END): automation.validate_automation( single=True ), + cv.Optional(CONF_ON_IDLE): automation.validate_automation(single=True), } ).extend(cv.COMPONENT_SCHEMA), tts_stream_validate, @@ -259,6 +263,13 @@ async def to_code(config): config[CONF_ON_TTS_STREAM_END], ) + if CONF_ON_IDLE in config: + await automation.build_automation( + var.get_idle_trigger(), + [], + config[CONF_ON_IDLE], + ) + cg.add_define("USE_VOICE_ASSISTANT") @@ -276,6 +287,7 @@ VOICE_ASSISTANT_ACTION_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(VoiceAssis VOICE_ASSISTANT_ACTION_SCHEMA.extend( { cv.Optional(CONF_SILENCE_DETECTION, default=True): cv.boolean, + cv.Optional(CONF_WAKE_WORD): cv.templatable(cv.string), } ), ) @@ -284,6 +296,9 @@ async def voice_assistant_listen_to_code(config, action_id, template_arg, args): await cg.register_parented(var, config[CONF_ID]) if CONF_SILENCE_DETECTION in config: cg.add(var.set_silence_detection(config[CONF_SILENCE_DETECTION])) + if wake_word := config.get(CONF_WAKE_WORD): + templ = await cg.templatable(wake_word, args, cg.std_string) + cg.add(var.set_wake_word(templ)) return var diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 29fc664342..34a26eec01 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -17,35 +17,31 @@ static const char *const TAG = "voice_assistant"; static const size_t SAMPLE_RATE_HZ = 16000; static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000; // 32ms * 16kHz / 1000ms -static const size_t BUFFER_SIZE = 1000 * SAMPLE_RATE_HZ / 1000; // 1s +static const size_t BUFFER_SIZE = 1024 * SAMPLE_RATE_HZ / 1000; static const size_t SEND_BUFFER_SIZE = INPUT_BUFFER_SIZE * sizeof(int16_t); static const size_t RECEIVE_SIZE = 1024; static const size_t SPEAKER_BUFFER_SIZE = 16 * RECEIVE_SIZE; float VoiceAssistant::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } -void VoiceAssistant::setup() { - ESP_LOGCONFIG(TAG, "Setting up Voice Assistant..."); - - global_voice_assistant = this; - +bool VoiceAssistant::start_udp_socket_() { this->socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); - if (socket_ == nullptr) { - ESP_LOGW(TAG, "Could not create socket"); + if (this->socket_ == nullptr) { + ESP_LOGE(TAG, "Could not create socket"); this->mark_failed(); - return; + return false; } int enable = 1; - int err = socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); if (err != 0) { ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err); // we can still continue } - err = socket_->setblocking(false); + err = this->socket_->setblocking(false); if (err != 0) { - ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err); + ESP_LOGE(TAG, "Socket unable to set nonblocking mode: errno %d", err); this->mark_failed(); - return; + return false; } #ifdef USE_SPEAKER @@ -54,18 +50,30 @@ void VoiceAssistant::setup() { socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), 6055); if (sl == 0) { - ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); + ESP_LOGE(TAG, "Socket unable to set sockaddr: errno %d", errno); this->mark_failed(); - return; + return false; } - err = socket_->bind((struct sockaddr *) &server, sizeof(server)); + err = this->socket_->bind((struct sockaddr *) &server, sizeof(server)); if (err != 0) { - ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno); + ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); this->mark_failed(); - return; + return false; } + } +#endif + this->udp_socket_running_ = true; + return true; +} +void VoiceAssistant::setup() { + ESP_LOGCONFIG(TAG, "Setting up Voice Assistant..."); + + global_voice_assistant = this; + +#ifdef USE_SPEAKER + if (this->speaker_ != nullptr) { ExternalRAMAllocator speaker_allocator(ExternalRAMAllocator::ALLOW_FAILURE); this->speaker_buffer_ = speaker_allocator.allocate(SPEAKER_BUFFER_SIZE); if (this->speaker_buffer_ == nullptr) { @@ -86,14 +94,14 @@ void VoiceAssistant::setup() { #ifdef USE_ESP_ADF this->vad_instance_ = vad_create(VAD_MODE_4); +#endif - this->ring_buffer_ = rb_create(BUFFER_SIZE, sizeof(int16_t)); + this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t)); if (this->ring_buffer_ == nullptr) { ESP_LOGW(TAG, "Could not allocate ring buffer"); this->mark_failed(); return; } -#endif ExternalRAMAllocator send_allocator(ExternalRAMAllocator::ALLOW_FAILURE); this->send_buffer_ = send_allocator.allocate(SEND_BUFFER_SIZE); @@ -112,14 +120,8 @@ int VoiceAssistant::read_microphone_() { memset(this->input_buffer_, 0, INPUT_BUFFER_SIZE * sizeof(int16_t)); return 0; } -#ifdef USE_ESP_ADF // Write audio into ring buffer - int available = rb_bytes_available(this->ring_buffer_); - if (available < bytes_read) { - rb_read(this->ring_buffer_, nullptr, bytes_read - available, 0); - } - rb_write(this->ring_buffer_, (char *) this->input_buffer_, bytes_read, 0); -#endif + this->ring_buffer_->write((void *) this->input_buffer_, bytes_read); } else { ESP_LOGD(TAG, "microphone not running"); } @@ -141,9 +143,11 @@ void VoiceAssistant::loop() { switch (this->state_) { case State::IDLE: { if (this->continuous_ && this->desired_state_ == State::IDLE) { + this->idle_trigger_->trigger(); + + this->ring_buffer_->reset(); #ifdef USE_ESP_ADF if (this->use_wake_word_) { - rb_reset(this->ring_buffer_); this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD); } else #endif @@ -219,6 +223,8 @@ void VoiceAssistant::loop() { msg.conversation_id = this->conversation_id_; msg.flags = flags; msg.audio_settings = audio_settings; + msg.wake_word_phrase = this->wake_word_; + this->wake_word_ = ""; if (this->api_client_ == nullptr || !this->api_client_->send_voice_assistant_request(msg)) { ESP_LOGW(TAG, "Could not request start"); @@ -236,19 +242,27 @@ void VoiceAssistant::loop() { break; // State changed when udp server port received } case State::STREAMING_MICROPHONE: { - size_t bytes_read = this->read_microphone_(); -#ifdef USE_ESP_ADF - if (rb_bytes_filled(this->ring_buffer_) >= SEND_BUFFER_SIZE) { - rb_read(this->ring_buffer_, (char *) this->send_buffer_, SEND_BUFFER_SIZE, 0); - this->socket_->sendto(this->send_buffer_, SEND_BUFFER_SIZE, 0, (struct sockaddr *) &this->dest_addr_, - sizeof(this->dest_addr_)); + this->read_microphone_(); + size_t available = this->ring_buffer_->available(); + while (available >= SEND_BUFFER_SIZE) { + size_t read_bytes = this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0); + if (this->audio_mode_ == AUDIO_MODE_API) { + api::VoiceAssistantAudio msg; + msg.data.assign((char *) this->send_buffer_, read_bytes); + this->api_client_->send_voice_assistant_audio(msg); + } else { + if (!this->udp_socket_running_) { + if (!this->start_udp_socket_()) { + this->set_state_(State::STOP_MICROPHONE, State::IDLE); + break; + } + } + this->socket_->sendto(this->send_buffer_, read_bytes, 0, (struct sockaddr *) &this->dest_addr_, + sizeof(this->dest_addr_)); + } + available = this->ring_buffer_->available(); } -#else - if (bytes_read > 0) { - this->socket_->sendto(this->input_buffer_, bytes_read, 0, (struct sockaddr *) &this->dest_addr_, - sizeof(this->dest_addr_)); - } -#endif + break; } case State::STOP_MICROPHONE: { @@ -274,22 +288,25 @@ void VoiceAssistant::loop() { #ifdef USE_SPEAKER if (this->speaker_ != nullptr) { ssize_t received_len = 0; - if (this->speaker_buffer_index_ + RECEIVE_SIZE < SPEAKER_BUFFER_SIZE) { - received_len = this->socket_->read(this->speaker_buffer_ + this->speaker_buffer_index_, RECEIVE_SIZE); - if (received_len > 0) { - this->speaker_buffer_index_ += received_len; - this->speaker_buffer_size_ += received_len; - this->speaker_bytes_received_ += received_len; + if (this->audio_mode_ == AUDIO_MODE_UDP) { + if (this->speaker_buffer_index_ + RECEIVE_SIZE < SPEAKER_BUFFER_SIZE) { + received_len = this->socket_->read(this->speaker_buffer_ + this->speaker_buffer_index_, RECEIVE_SIZE); + if (received_len > 0) { + this->speaker_buffer_index_ += received_len; + this->speaker_buffer_size_ += received_len; + this->speaker_bytes_received_ += received_len; + } + } else { + ESP_LOGD(TAG, "Receive buffer full"); } - } else { - ESP_LOGD(TAG, "Receive buffer full"); } // Build a small buffer of audio before sending to the speaker - if (this->speaker_bytes_received_ > RECEIVE_SIZE * 4) + bool end_of_stream = this->stream_ended_ && (this->audio_mode_ == AUDIO_MODE_API || received_len < 0); + if (this->speaker_bytes_received_ > RECEIVE_SIZE * 4 || end_of_stream) this->write_speaker_(); if (this->wait_for_stream_end_) { this->cancel_timeout("playing"); - if (this->stream_ended_ && received_len < 0) { + if (end_of_stream) { ESP_LOGD(TAG, "End of audio stream received"); this->cancel_timeout("speaker-timeout"); this->set_state_(State::RESPONSE_FINISHED, State::RESPONSE_FINISHED); @@ -434,6 +451,22 @@ void VoiceAssistant::failed_to_start() { this->set_state_(State::STOP_MICROPHONE, State::IDLE); } +void VoiceAssistant::start_streaming() { + if (this->state_ != State::STARTING_PIPELINE) { + this->signal_stop_(); + return; + } + + ESP_LOGD(TAG, "Client started, streaming microphone"); + this->audio_mode_ = AUDIO_MODE_API; + + if (this->mic_->is_running()) { + this->set_state_(State::STREAMING_MICROPHONE, State::STREAMING_MICROPHONE); + } else { + this->set_state_(State::START_MICROPHONE, State::STREAMING_MICROPHONE); + } +} + void VoiceAssistant::start_streaming(struct sockaddr_storage *addr, uint16_t port) { if (this->state_ != State::STARTING_PIPELINE) { this->signal_stop_(); @@ -441,6 +474,7 @@ void VoiceAssistant::start_streaming(struct sockaddr_storage *addr, uint16_t por } ESP_LOGD(TAG, "Client started, streaming microphone"); + this->audio_mode_ = AUDIO_MODE_UDP; memcpy(&this->dest_addr_, addr, sizeof(this->dest_addr_)); if (this->dest_addr_.ss_family == AF_INET) { @@ -473,9 +507,9 @@ void VoiceAssistant::request_start(bool continuous, bool silence_detection) { if (this->state_ == State::IDLE) { this->continuous_ = continuous; this->silence_detection_ = silence_detection; + this->ring_buffer_->reset(); #ifdef USE_ESP_ADF if (this->use_wake_word_) { - rb_reset(this->ring_buffer_); this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD); } else #endif @@ -618,9 +652,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { case api::enums::VOICE_ASSISTANT_RUN_END: { ESP_LOGD(TAG, "Assist Pipeline ended"); if (this->state_ == State::STREAMING_MICROPHONE) { + this->ring_buffer_->reset(); #ifdef USE_ESP_ADF if (this->use_wake_word_) { - rb_reset(this->ring_buffer_); // No need to stop the microphone since we didn't use the speaker this->set_state_(State::WAIT_FOR_VAD, State::WAITING_FOR_VAD); } else @@ -628,6 +662,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { { this->set_state_(State::IDLE, State::IDLE); } + } else if (this->state_ == State::AWAITING_RESPONSE) { + // No TTS start event ("nevermind") + this->set_state_(State::IDLE, State::IDLE); } this->defer([this]() { this->end_trigger_->trigger(); }); break; @@ -691,6 +728,17 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } } +void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) { + if (this->speaker_buffer_index_ + msg.data.length() < SPEAKER_BUFFER_SIZE) { + memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data.data(), msg.data.length()); + this->speaker_buffer_index_ += msg.data.length(); + this->speaker_buffer_size_ += msg.data.length(); + this->speaker_bytes_received_ += msg.data.length(); + } else { + ESP_LOGE(TAG, "Cannot receive audio, buffer is full"); + } +} + VoiceAssistant *global_voice_assistant = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace voice_assistant diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index f9325dff54..d6b1502381 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -7,6 +7,7 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/ring_buffer.h" #include "esphome/components/api/api_connection.h" #include "esphome/components/api/api_pb2.h" @@ -21,7 +22,6 @@ #ifdef USE_ESP_ADF #include -#include #endif namespace esphome { @@ -29,9 +29,14 @@ namespace voice_assistant { // Version 1: Initial version // Version 2: Adds raw speaker support -// Version 3: Unused/skip -static const uint32_t INITIAL_VERSION = 1; -static const uint32_t SPEAKER_SUPPORT = 2; +static const uint32_t LEGACY_INITIAL_VERSION = 1; +static const uint32_t LEGACY_SPEAKER_SUPPORT = 2; + +enum VoiceAssistantFeature : uint32_t { + FEATURE_VOICE_ASSISTANT = 1 << 0, + FEATURE_SPEAKER = 1 << 1, + FEATURE_API_AUDIO = 1 << 2, +}; enum class State { IDLE, @@ -49,11 +54,17 @@ enum class State { RESPONSE_FINISHED, }; +enum AudioMode : uint8_t { + AUDIO_MODE_UDP, + AUDIO_MODE_API, +}; + class VoiceAssistant : public Component { public: void setup() override; void loop() override; float get_setup_priority() const override; + void start_streaming(); void start_streaming(struct sockaddr_storage *addr, uint16_t port); void failed_to_start(); @@ -71,19 +82,32 @@ class VoiceAssistant : public Component { } #endif - uint32_t get_version() const { + uint32_t get_legacy_version() const { #ifdef USE_SPEAKER if (this->speaker_ != nullptr) { - return SPEAKER_SUPPORT; + return LEGACY_SPEAKER_SUPPORT; } #endif - return INITIAL_VERSION; + return LEGACY_INITIAL_VERSION; + } + + uint32_t get_feature_flags() const { + uint32_t flags = 0; + flags |= VoiceAssistantFeature::FEATURE_VOICE_ASSISTANT; +#ifdef USE_SPEAKER + if (this->speaker_ != nullptr) { + flags |= VoiceAssistantFeature::FEATURE_SPEAKER; + flags |= VoiceAssistantFeature::FEATURE_API_AUDIO; + } +#endif + return flags; } void request_start(bool continuous, bool silence_detection); void request_stop(); void on_event(const api::VoiceAssistantEventResponse &msg); + void on_audio(const api::VoiceAssistantAudio &msg); bool is_running() const { return this->state_ != State::IDLE; } void set_continuous(bool continuous) { this->continuous_ = continuous; } @@ -116,6 +140,7 @@ class VoiceAssistant : public Component { Trigger *get_tts_end_trigger() const { return this->tts_end_trigger_; } Trigger *get_tts_start_trigger() const { return this->tts_start_trigger_; } Trigger *get_error_trigger() const { return this->error_trigger_; } + Trigger<> *get_idle_trigger() const { return this->idle_trigger_; } Trigger<> *get_client_connected_trigger() const { return this->client_connected_trigger_; } Trigger<> *get_client_disconnected_trigger() const { return this->client_disconnected_trigger_; } @@ -123,6 +148,8 @@ class VoiceAssistant : public Component { void client_subscription(api::APIConnection *client, bool subscribe); api::APIConnection *get_api_connection() const { return this->api_client_; } + void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; } + protected: int read_microphone_(); void set_state_(State state); @@ -148,6 +175,7 @@ class VoiceAssistant : public Component { Trigger *tts_end_trigger_ = new Trigger(); Trigger *tts_start_trigger_ = new Trigger(); Trigger *error_trigger_ = new Trigger(); + Trigger<> *idle_trigger_ = new Trigger<>(); Trigger<> *client_connected_trigger_ = new Trigger<>(); Trigger<> *client_disconnected_trigger_ = new Trigger<>(); @@ -173,14 +201,16 @@ class VoiceAssistant : public Component { std::string conversation_id_{""}; + std::string wake_word_{""}; + HighFrequencyLoopRequester high_freq_; #ifdef USE_ESP_ADF vad_handle_t vad_instance_; - ringbuf_handle_t ring_buffer_; uint8_t vad_threshold_{5}; uint8_t vad_counter_{0}; #endif + std::unique_ptr ring_buffer_; bool use_wake_word_; uint8_t noise_suppression_level_; @@ -195,11 +225,20 @@ class VoiceAssistant : public Component { State state_{State::IDLE}; State desired_state_{State::IDLE}; + + AudioMode audio_mode_{AUDIO_MODE_UDP}; + bool udp_socket_running_{false}; + bool start_udp_socket_(); }; template class StartAction : public Action, public Parented { + TEMPLATABLE_VALUE(std::string, wake_word); + public: - void play(Ts... x) override { this->parent_->request_start(false, this->silence_detection_); } + void play(Ts... x) override { + this->parent_->set_wake_word(this->wake_word_.value(x...)); + this->parent_->request_start(false, this->silence_detection_); + } void set_silence_detection(bool silence_detection) { this->silence_detection_ = silence_detection; } diff --git a/esphome/components/wake_on_lan/wake_on_lan.cpp b/esphome/components/wake_on_lan/wake_on_lan.cpp index a4dd0f3b6f..f414bf6c71 100644 --- a/esphome/components/wake_on_lan/wake_on_lan.cpp +++ b/esphome/components/wake_on_lan/wake_on_lan.cpp @@ -32,8 +32,12 @@ void WakeOnLanButton::press_action() { bool end_status = false; IPAddress broadcast = IPAddress(255, 255, 255, 255); #ifdef USE_ESP8266 - begin_status = this->udp_client_.beginPacketMulticast(broadcast, 9, - IPAddress((ip_addr_t) esphome::network::get_ip_address()), 128); + for (auto ip : esphome::network::get_ip_addresses()) { + if (ip.is_ip4()) { + begin_status = this->udp_client_.beginPacketMulticast(broadcast, 9, ip, 128); + break; + } + } #endif #ifdef USE_ESP32 begin_status = this->udp_client_.beginPacket(broadcast, 9); diff --git a/esphome/components/waveshare_epaper/__init__.py b/esphome/components/waveshare_epaper/__init__.py index e69de29bb2..c58ce8a01e 100644 --- a/esphome/components/waveshare_epaper/__init__.py +++ b/esphome/components/waveshare_epaper/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@clydebarrow"] diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 519b07fca2..dc43cbf5a7 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -17,8 +17,12 @@ from esphome.const import ( DEPENDENCIES = ["spi"] waveshare_epaper_ns = cg.esphome_ns.namespace("waveshare_epaper") -WaveshareEPaper = waveshare_epaper_ns.class_( - "WaveshareEPaper", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer +WaveshareEPaperBase = waveshare_epaper_ns.class_( + "WaveshareEPaperBase", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer +) +WaveshareEPaper = waveshare_epaper_ns.class_("WaveshareEPaper", WaveshareEPaperBase) +WaveshareEPaperBWR = waveshare_epaper_ns.class_( + "WaveshareEPaperBWR", WaveshareEPaperBase ) WaveshareEPaperTypeA = waveshare_epaper_ns.class_( "WaveshareEPaperTypeA", WaveshareEPaper @@ -26,9 +30,24 @@ WaveshareEPaperTypeA = waveshare_epaper_ns.class_( WaveshareEPaper2P7In = waveshare_epaper_ns.class_( "WaveshareEPaper2P7In", WaveshareEPaper ) +WaveshareEPaper2P7InB = waveshare_epaper_ns.class_( + "WaveshareEPaper2P7InB", WaveshareEPaperBWR +) +WaveshareEPaper2P7InBV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P7InBV2", WaveshareEPaperBWR +) +WaveshareEPaper2P7InV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P7InV2", WaveshareEPaper +) WaveshareEPaper2P9InB = waveshare_epaper_ns.class_( "WaveshareEPaper2P9InB", WaveshareEPaper ) +WaveshareEPaper2P9InBV3 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P9InBV3", WaveshareEPaper +) +WaveshareEPaper2P9InV2R2 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P9InV2R2", WaveshareEPaper +) GDEY029T94 = waveshare_epaper_ns.class_("GDEY029T94", WaveshareEPaper) WaveshareEPaper4P2In = waveshare_epaper_ns.class_( "WaveshareEPaper4P2In", WaveshareEPaper @@ -66,6 +85,12 @@ WaveshareEPaper7P5InHDB = waveshare_epaper_ns.class_( WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_( "WaveshareEPaper2P13InDKE", WaveshareEPaper ) +WaveshareEPaper2P13InV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P13InV2", WaveshareEPaper +) +WaveshareEPaper2P13InV3 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P13InV3", WaveshareEPaper +) GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper) WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel") @@ -75,6 +100,7 @@ MODELS = { "1.54in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN), "1.54inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN_V2), "2.13in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN), + "2.13inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN_V2), "2.13in-ttgo": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN), "2.13in-ttgo-b1": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B1), "2.13in-ttgo-b73": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B73), @@ -83,7 +109,12 @@ MODELS = { "2.90inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN_V2), "gdey029t94": ("c", GDEY029T94), "2.70in": ("b", WaveshareEPaper2P7In), + "2.70in-b": ("b", WaveshareEPaper2P7InB), + "2.70in-bv2": ("b", WaveshareEPaper2P7InBV2), + "2.70inv2": ("b", WaveshareEPaper2P7InV2), "2.90in-b": ("b", WaveshareEPaper2P9InB), + "2.90in-bv3": ("b", WaveshareEPaper2P9InBV3), + "2.90inv2-r2": ("c", WaveshareEPaper2P9InV2R2), "4.20in": ("b", WaveshareEPaper4P2In), "4.20in-bv2": ("b", WaveshareEPaper4P2InBV2), "5.83in": ("b", WaveshareEPaper5P8In), @@ -96,9 +127,12 @@ MODELS = { "7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt), "7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB), "2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE), + "2.13inv3": ("c", WaveshareEPaper2P13InV3), "1.54in-m5coreink-m09": ("c", GDEW0154M09), } +RESET_PIN_REQUIRED_MODELS = ("2.13inv2", "2.13in-ttgo-b74") + def validate_full_update_every_only_types_ac(value): if CONF_FULL_UPDATE_EVERY not in value: @@ -115,15 +149,23 @@ def validate_full_update_every_only_types_ac(value): return value +def validate_reset_pin_required(config): + if config[CONF_MODEL] in RESET_PIN_REQUIRED_MODELS and CONF_RESET_PIN not in config: + raise cv.Invalid( + f"'{CONF_RESET_PIN}' is required for model {config[CONF_MODEL]}" + ) + return config + + CONFIG_SCHEMA = cv.All( display.FULL_DISPLAY_SCHEMA.extend( { - cv.GenerateID(): cv.declare_id(WaveshareEPaper), + cv.GenerateID(): cv.declare_id(WaveshareEPaperBase), cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True), cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema, - cv.Optional(CONF_FULL_UPDATE_EVERY): cv.uint32_t, + cv.Optional(CONF_FULL_UPDATE_EVERY): cv.int_range(min=1, max=4294967295), cv.Optional(CONF_RESET_DURATION): cv.All( cv.positive_time_period_milliseconds, cv.Range(max=core.TimePeriod(milliseconds=500)), @@ -133,6 +175,7 @@ CONFIG_SCHEMA = cv.All( .extend(cv.polling_component_schema("1s")) .extend(spi.spi_device_schema()), validate_full_update_every_only_types_ac, + validate_reset_pin_required, cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), ) diff --git a/esphome/components/waveshare_epaper/waveshare_213v3.cpp b/esphome/components/waveshare_epaper/waveshare_213v3.cpp new file mode 100644 index 0000000000..196aeed3f7 --- /dev/null +++ b/esphome/components/waveshare_epaper/waveshare_213v3.cpp @@ -0,0 +1,186 @@ +#include "waveshare_epaper.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace waveshare_epaper { + +static const char *const TAG = "waveshare_2.13v3"; + +static const uint8_t PARTIAL_LUT[] = { + 0x32, // cmd + 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, +}; + +static const uint8_t FULL_LUT[] = { + 0x32, // CMD + 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x4A, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0x0, 0xF, 0x0, 0x0, 0x2, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, +}; + +static const uint8_t SW_RESET = 0x12; +static const uint8_t ACTIVATE = 0x20; +static const uint8_t WRITE_BUFFER = 0x24; +static const uint8_t WRITE_BASE = 0x26; + +static const uint8_t DRV_OUT_CTL[] = {0x01, 0x27, 0x01, 0x00}; // driver output control +static const uint8_t GATEV[] = {0x03, 0x17}; +static const uint8_t SRCV[] = {0x04, 0x41, 0x0C, 0x32}; +static const uint8_t SLEEP[] = {0x10, 0x01}; +static const uint8_t DATA_ENTRY[] = {0x11, 0x03}; // data entry mode +static const uint8_t TEMP_SENS[] = {0x18, 0x80}; // Temp sensor +static const uint8_t DISPLAY_UPDATE[] = {0x21, 0x00, 0x80}; // Display update control +static const uint8_t UPSEQ[] = {0x22, 0xC0}; +static const uint8_t ON_FULL[] = {0x22, 0xC7}; +static const uint8_t ON_PARTIAL[] = {0x22, 0x0F}; +static const uint8_t VCOM[] = {0x2C, 0x36}; +static const uint8_t CMD5[] = {0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00}; +static const uint8_t BORDER_PART[] = {0x3C, 0x80}; // border waveform +static const uint8_t BORDER_FULL[] = {0x3C, 0x05}; // border waveform +static const uint8_t CMD1[] = {0x3F, 0x22}; +static const uint8_t RAM_X_START[] = {0x44, 0x00, 121 / 8}; // set ram_x_address_start_end +static const uint8_t RAM_Y_START[] = {0x45, 0x00, 0x00, 250 - 1, 0}; // set ram_y_address_start_end +static const uint8_t RAM_X_POS[] = {0x4E, 0x00}; // set ram_x_address_counter +// static const uint8_t RAM_Y_POS[] = {0x4F, 0x00, 0x00}; // set ram_y_address_counter +#define SEND(x) this->cmd_data(x, sizeof(x)) + +void WaveshareEPaper2P13InV3::write_lut_(const uint8_t *lut) { + this->wait_until_idle_(); + this->cmd_data(lut, sizeof(PARTIAL_LUT)); + SEND(CMD1); + SEND(GATEV); + SEND(SRCV); + SEND(VCOM); +} + +// write the buffer starting on line top, up to line bottom. +void WaveshareEPaper2P13InV3::write_buffer_(uint8_t cmd, int top, int bottom) { + this->wait_until_idle_(); + this->set_window_(top, bottom); + this->command(cmd); + this->start_data_(); + auto width_bytes = this->get_width_internal() / 8; + this->write_array(this->buffer_ + top * width_bytes, (bottom - top) * width_bytes); + this->end_data_(); +} + +void WaveshareEPaper2P13InV3::send_reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(2); + this->reset_pin_->digital_write(true); + } +} + +void WaveshareEPaper2P13InV3::setup() { + setup_pins_(); + delay(20); + this->send_reset_(); + // as a one-off delay this is not worth working around. + delay(100); // NOLINT + this->wait_until_idle_(); + this->command(SW_RESET); + this->wait_until_idle_(); + + SEND(DRV_OUT_CTL); + SEND(DATA_ENTRY); + SEND(CMD5); + this->set_window_(0, this->get_height_internal()); + SEND(BORDER_FULL); + SEND(DISPLAY_UPDATE); + SEND(TEMP_SENS); + this->wait_until_idle_(); + this->write_lut_(FULL_LUT); +} + +// t and b are y positions, i.e. line numbers. +void WaveshareEPaper2P13InV3::set_window_(int t, int b) { + uint8_t buffer[3]; + + SEND(RAM_X_START); + SEND(RAM_Y_START); + SEND(RAM_X_POS); + buffer[0] = 0x4F; + buffer[1] = (uint8_t) t; + buffer[2] = (uint8_t) (t >> 8); + SEND(buffer); +} + +// must implement, but we override setup to have more control +void WaveshareEPaper2P13InV3::initialize() {} + +void WaveshareEPaper2P13InV3::partial_update_() { + this->send_reset_(); + this->set_timeout(100, [this] { + this->write_lut_(PARTIAL_LUT); + SEND(BORDER_PART); + SEND(UPSEQ); + this->command(ACTIVATE); + this->set_timeout(100, [this] { + this->wait_until_idle_(); + this->write_buffer_(WRITE_BUFFER, 0, this->get_height_internal()); + SEND(ON_PARTIAL); + this->command(ACTIVATE); // Activate Display Update Sequence + this->is_busy_ = false; + }); + }); +} + +void WaveshareEPaper2P13InV3::full_update_() { + ESP_LOGI(TAG, "Performing full e-paper update."); + this->write_lut_(FULL_LUT); + this->write_buffer_(WRITE_BUFFER, 0, this->get_height_internal()); + this->write_buffer_(WRITE_BASE, 0, this->get_height_internal()); + SEND(ON_FULL); + this->command(ACTIVATE); // don't wait here + this->is_busy_ = false; +} + +void WaveshareEPaper2P13InV3::display() { + if (this->is_busy_ || (this->busy_pin_ != nullptr && this->busy_pin_->digital_read())) + return; + this->is_busy_ = true; + const bool partial = this->at_update_ != 0; + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; + if (partial) { + this->partial_update_(); + } else { + this->full_update_(); + } +} + +int WaveshareEPaper2P13InV3::get_width_internal() { return 128; } + +int WaveshareEPaper2P13InV3::get_height_internal() { return 250; } + +uint32_t WaveshareEPaper2P13InV3::idle_timeout_() { return 5000; } + +void WaveshareEPaper2P13InV3::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this) + ESP_LOGCONFIG(TAG, " Model: 2.13inV3"); + LOG_PIN(" CS Pin: ", this->cs_) + LOG_PIN(" Reset Pin: ", this->reset_pin_) + LOG_PIN(" DC Pin: ", this->dc_pin_) + LOG_PIN(" Busy Pin: ", this->busy_pin_) + LOG_UPDATE_INTERVAL(this) +} + +void WaveshareEPaper2P13InV3::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} + +} // namespace waveshare_epaper +} // namespace esphome diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 8fdb9a3ac0..cf43c4cc32 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -83,7 +83,34 @@ static const uint8_t PARTIAL_UPDATE_LUT_TTGO_B1[LUT_SIZE_TTGO_B1] = { 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; -void WaveshareEPaper::setup_pins_() { +// clang-format off +// Disable formatting to preserve the same look as in Waveshare examples +static const uint8_t PARTIAL_UPD_2IN9_LUT_SIZE = 159; +static const uint8_t PARTIAL_UPD_2IN9_LUT[PARTIAL_UPD_2IN9_LUT_SIZE] = +{ + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, + 0x22, 0x17, 0x41, 0xB0, 0x32, 0x36, +}; +// clang-format on + +void WaveshareEPaperBase::setup_pins_() { this->init_internal_(this->get_buffer_length_()); this->dc_pin_->setup(); // OUTPUT this->dc_pin_->digital_write(false); @@ -98,19 +125,31 @@ void WaveshareEPaper::setup_pins_() { this->reset_(); } -float WaveshareEPaper::get_setup_priority() const { return setup_priority::PROCESSOR; } -void WaveshareEPaper::command(uint8_t value) { +float WaveshareEPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; } +void WaveshareEPaperBase::command(uint8_t value) { this->start_command_(); this->write_byte(value); this->end_command_(); } -void WaveshareEPaper::data(uint8_t value) { +void WaveshareEPaperBase::data(uint8_t value) { this->start_data_(); this->write_byte(value); this->end_data_(); } -bool WaveshareEPaper::wait_until_idle_() { - if (this->busy_pin_ == nullptr) { + +// write a command followed by one or more bytes of data. +// The command is the first byte, length is the total including cmd. +void WaveshareEPaperBase::cmd_data(const uint8_t *c_data, size_t length) { + this->dc_pin_->digital_write(false); + this->enable(); + this->write_byte(c_data[0]); + this->dc_pin_->digital_write(true); + this->write_array(c_data + 1, length - 1); + this->disable(); +} + +bool WaveshareEPaperBase::wait_until_idle_() { + if (this->busy_pin_ == nullptr || !this->busy_pin_->digital_read()) { return true; } @@ -120,11 +159,11 @@ bool WaveshareEPaper::wait_until_idle_() { ESP_LOGE(TAG, "Timeout while displaying image!"); return false; } - delay(10); + delay(1); } return true; } -void WaveshareEPaper::update() { +void WaveshareEPaperBase::update() { this->do_update_(); this->display(); } @@ -147,20 +186,51 @@ void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color this->buffer_[pos] &= ~(0x80 >> subpos); } } + uint32_t WaveshareEPaper::get_buffer_length_() { return this->get_width_controller() * this->get_height_internal() / 8u; +} // just a black buffer +uint32_t WaveshareEPaperBWR::get_buffer_length_() { + return this->get_width_controller() * this->get_height_internal() / 4u; +} // black and red buffer + +void WaveshareEPaperBWR::fill(Color color) { + this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); } -void WaveshareEPaper::start_command_() { +void HOT WaveshareEPaperBWR::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) + return; + + const uint32_t buf_half_len = this->get_buffer_length_() / 2u; + + const uint32_t pos = (x + y * this->get_width_internal()) / 8u; + const uint8_t subpos = x & 0x07; + // flip logic + if (color.is_on()) { + this->buffer_[pos] |= 0x80 >> subpos; + } else { + this->buffer_[pos] &= ~(0x80 >> subpos); + } + + // draw red pixels only, if the color contains red only + if (((color.red > 0) && (color.green == 0) && (color.blue == 0))) { + this->buffer_[pos + buf_half_len] |= 0x80 >> subpos; + } else { + this->buffer_[pos + buf_half_len] &= ~(0x80 >> subpos); + } +} + +void WaveshareEPaperBase::start_command_() { this->dc_pin_->digital_write(false); this->enable(); } -void WaveshareEPaper::end_command_() { this->disable(); } -void WaveshareEPaper::start_data_() { +void WaveshareEPaperBase::end_command_() { this->disable(); } +void WaveshareEPaperBase::start_data_() { this->dc_pin_->digital_write(true); this->enable(); } -void WaveshareEPaper::end_data_() { this->disable(); } -void WaveshareEPaper::on_safe_shutdown() { this->deep_sleep(); } +void WaveshareEPaperBase::end_data_() { this->disable(); } +void WaveshareEPaperBase::on_safe_shutdown() { this->deep_sleep(); } // ======================================================== // Type A @@ -186,12 +256,14 @@ void WaveshareEPaperTypeA::initialize() { } } void WaveshareEPaperTypeA::init_display_() { - if (this->model_ == TTGO_EPAPER_2_13_IN_B74) { - this->reset_pin_->digital_write(false); - delay(10); - this->reset_pin_->digital_write(true); - delay(10); - this->wait_until_idle_(); + if (this->model_ == TTGO_EPAPER_2_13_IN_B74 || this->model_ == WAVESHARE_EPAPER_2_13_IN_V2) { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(10); + this->reset_pin_->digital_write(true); + delay(10); + this->wait_until_idle_(); + } this->command(0x12); // SWRESET this->wait_until_idle_(); @@ -251,6 +323,9 @@ void WaveshareEPaperTypeA::dump_config() { case WAVESHARE_EPAPER_2_13_IN: ESP_LOGCONFIG(TAG, " Model: 2.13in"); break; + case WAVESHARE_EPAPER_2_13_IN_V2: + ESP_LOGCONFIG(TAG, " Model: 2.13inV2"); + break; case TTGO_EPAPER_2_13_IN: ESP_LOGCONFIG(TAG, " Model: 2.13in (TTGO)"); break; @@ -296,6 +371,8 @@ void HOT WaveshareEPaperTypeA::display() { if (full_update != prev_full_update) { switch (this->model_) { case TTGO_EPAPER_2_13_IN: + case WAVESHARE_EPAPER_2_13_IN_V2: + // Waveshare 2.13" V2 uses the same LUTs as TTGO this->write_lut_(full_update ? FULL_UPDATE_LUT_TTGO : PARTIAL_UPDATE_LUT_TTGO, LUT_SIZE_TTGO); break; case TTGO_EPAPER_2_13_IN_B73: @@ -314,6 +391,41 @@ void HOT WaveshareEPaperTypeA::display() { this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; } + if (this->model_ == WAVESHARE_EPAPER_2_13_IN_V2) { + // Set VCOM for full or partial update + this->command(0x2C); + this->data(full_update ? 0x55 : 0x26); + + if (!full_update) { + // Enable "ping-pong" + this->command(0x37); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x40); + this->data(0x00); + this->data(0x00); + this->command(0x22); + this->data(0xc0); + this->command(0x20); + } + } + + // Border waveform + switch (this->model_) { + case TTGO_EPAPER_2_13_IN_B74: + this->command(0x3C); + this->data(full_update ? 0x05 : 0x80); + break; + case WAVESHARE_EPAPER_2_13_IN_V2: + this->command(0x3C); + this->data(full_update ? 0x03 : 0x01); + break; + default: + break; + } + // Set x & y regions we want to write to (full) switch (this->model_) { case TTGO_EPAPER_2_13_IN_B1: @@ -337,12 +449,6 @@ void HOT WaveshareEPaperTypeA::display() { this->data((this->get_height_internal() - 1) >> 8); break; - case TTGO_EPAPER_2_13_IN_B74: - // BorderWaveform - this->command(0x3C); - this->data(full_update ? 0x05 : 0x80); - - // fall through default: // COMMAND SET RAM X ADDRESS START END POSITION this->command(0x44); @@ -388,6 +494,14 @@ void HOT WaveshareEPaperTypeA::display() { } this->end_data_(); + if (this->model_ == WAVESHARE_EPAPER_2_13_IN_V2 && full_update) { + // Write base image again on full refresh + this->command(0x26); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + } + // COMMAND DISPLAY UPDATE CONTROL 2 this->command(0x22); switch (this->model_) { @@ -399,6 +513,9 @@ void HOT WaveshareEPaperTypeA::display() { case TTGO_EPAPER_2_13_IN_B73: this->data(0xC7); break; + case WAVESHARE_EPAPER_2_13_IN_V2: + this->data(full_update ? 0xC7 : 0x0C); + break; default: this->data(0xC4); break; @@ -422,6 +539,7 @@ int WaveshareEPaperTypeA::get_width_internal() { case WAVESHARE_EPAPER_1_54_IN_V2: return 200; case WAVESHARE_EPAPER_2_13_IN: + case WAVESHARE_EPAPER_2_13_IN_V2: case TTGO_EPAPER_2_13_IN: case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: @@ -437,6 +555,7 @@ int WaveshareEPaperTypeA::get_width_internal() { int WaveshareEPaperTypeA::get_width_controller() { switch (this->model_) { case WAVESHARE_EPAPER_2_13_IN: + case WAVESHARE_EPAPER_2_13_IN_V2: case TTGO_EPAPER_2_13_IN: case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: @@ -452,6 +571,7 @@ int WaveshareEPaperTypeA::get_height_internal() { case WAVESHARE_EPAPER_1_54_IN_V2: return 200; case WAVESHARE_EPAPER_2_13_IN: + case WAVESHARE_EPAPER_2_13_IN_V2: case TTGO_EPAPER_2_13_IN: case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: @@ -478,10 +598,11 @@ uint32_t WaveshareEPaperTypeA::idle_timeout_() { switch (this->model_) { case WAVESHARE_EPAPER_1_54_IN: case WAVESHARE_EPAPER_1_54_IN_V2: + case WAVESHARE_EPAPER_2_13_IN_V2: case TTGO_EPAPER_2_13_IN_B1: return 2500; default: - return WaveshareEPaper::idle_timeout_(); + return WaveshareEPaperBase::idle_timeout_(); } } @@ -634,6 +755,299 @@ void WaveshareEPaper2P7In::dump_config() { LOG_UPDATE_INTERVAL(this); } +void WaveshareEPaper2P7InV2::initialize() { + this->reset_(); + this->wait_until_idle_(); + + this->command(0x12); // SWRESET + this->wait_until_idle_(); + + // SET WINDOWS + // XRAM_START_AND_END_POSITION + this->command(0x44); + this->data(0x00); + this->data(((get_width_internal() - 1) >> 3) & 0xFF); + // YRAM_START_AND_END_POSITION + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data((get_height_internal() - 1) & 0xFF); + this->data(((get_height_internal() - 1) >> 8) & 0xFF); + + // SET CURSOR + // XRAM_ADDRESS + this->command(0x4E); + this->data(0x00); + // YRAM_ADDRESS + this->command(0x4F); + this->data(0x00); + this->data(0x00); + + this->command(0x11); // data entry mode + this->data(0x03); +} +void HOT WaveshareEPaper2P7InV2::display() { + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // COMMAND DISPLAY REFRESH + this->command(0x22); + this->data(0xF7); + this->command(0x20); +} +int WaveshareEPaper2P7InV2::get_width_internal() { return 176; } +int WaveshareEPaper2P7InV2::get_height_internal() { return 264; } +void WaveshareEPaper2P7InV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.7in V2"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// 2.7inch_e-paper_b +// ======================================================== +// Datasheet: +// - https://www.waveshare.com/w/upload/d/d8/2.7inch-e-paper-b-specification.pdf +// - https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_2in7b.c + +static const uint8_t LUT_VCOM_DC_2_7B[44] = {0x00, 0x00, 0x00, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x0A, + 0x00, 0x00, 0x08, 0x00, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x00, 0x0A, + 0x0A, 0x00, 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, + 0x03, 0x0E, 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01}; + +static const uint8_t LUT_WHITE_TO_WHITE_2_7B[42] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x0A, 0x00, 0x00, + 0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x80, 0x0A, 0x0A, 0x00, + 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E, + 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01}; + +static const uint8_t LUT_BLACK_TO_WHITE_2_7B[42] = {0xA0, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x0A, 0x00, 0x00, + 0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x90, 0x0A, 0x0A, 0x00, + 0x00, 0x08, 0xB0, 0x04, 0x10, 0x00, 0x00, 0x05, 0xB0, 0x03, 0x0E, + 0x00, 0x00, 0x0A, 0xC0, 0x23, 0x00, 0x00, 0x00, 0x01}; + +static const uint8_t LUT_WHITE_TO_BLACK_2_7B[] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x20, 0x0A, 0x0A, 0x00, 0x00, + 0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x10, 0x0A, 0x0A, 0x00, + 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E, + 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01}; + +static const uint8_t LUT_BLACK_TO_BLACK_2_7B[42] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x0A, 0x00, 0x00, + 0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x80, 0x0A, 0x0A, 0x00, + 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E, + 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01}; + +void WaveshareEPaper2P7InB::initialize() { + this->reset_(); + + // command power on + this->command(0x04); + this->wait_until_idle_(); + delay(10); + + // Command panel setting + this->command(0x00); + this->data(0xAF); // KW-BF KWR-AF BWROTP 0f + // command pll control + this->command(0x30); + this->data(0x3A); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ + + // command power setting + this->command(0x01); + this->data(0x03); // VDS_EN, VDG_EN + this->data(0x00); // VCOM_HV, VGHL_LV[1], VGHL_LV[0] + this->data(0x2B); // VDH + this->data(0x2B); // VDL + this->data(0x09); // VDHR + + // command booster soft start + this->command(0x06); + this->data(0x07); + this->data(0x07); + this->data(0x17); + + // Power optimization - ??? + this->command(0xF8); + this->data(0x60); + this->data(0xA5); + this->command(0xF8); + this->data(0x89); + this->data(0xA5); + this->command(0xF8); + this->data(0x90); + this->data(0x00); + this->command(0xF8); + this->data(0x93); + this->data(0x2A); + this->command(0xF8); + this->data(0x73); + this->data(0x41); + + // COMMAND VCM DC SETTING + this->command(0x82); + this->data(0x12); + + // VCOM_AND_DATA_INTERVAL_SETTING + this->command(0x50); + this->data(0x87); // define by OTP + + delay(2); + // COMMAND LUT FOR VCOM + this->command(0x20); + for (uint8_t i : LUT_VCOM_DC_2_7B) + this->data(i); + // COMMAND LUT WHITE TO WHITE + this->command(0x21); + for (uint8_t i : LUT_WHITE_TO_WHITE_2_7B) + this->data(i); + // COMMAND LUT BLACK TO WHITE + this->command(0x22); + for (uint8_t i : LUT_BLACK_TO_WHITE_2_7B) + this->data(i); + // COMMAND LUT WHITE TO BLACK + this->command(0x23); + for (uint8_t i : LUT_WHITE_TO_BLACK_2_7B) { + this->data(i); + } + // COMMAND LUT BLACK TO BLACK + this->command(0x24); + + for (uint8_t i : LUT_BLACK_TO_BLACK_2_7B) { + this->data(i); + } + + delay(2); +} + +void HOT WaveshareEPaper2P7InB::display() { + uint32_t buf_len_half = this->get_buffer_length_() >> 1; + this->initialize(); + + // TCON_RESOLUTION + this->command(0x61); + this->data(this->get_width_internal() >> 8); + this->data(this->get_width_internal() & 0xff); // 176 + this->data(this->get_height_internal() >> 8); + this->data(this->get_height_internal() & 0xff); // 264 + + // COMMAND DATA START TRANSMISSION 1 (BLACK) + this->command(0x10); + delay(2); + for (uint32_t i = 0; i < buf_len_half; i++) { + this->data(this->buffer_[i]); + } + this->command(0x11); + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED) + this->command(0x13); + delay(2); + for (uint32_t i = buf_len_half; i < buf_len_half * 2u; i++) { + this->data(this->buffer_[i]); + } + this->command(0x11); + + delay(2); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + this->wait_until_idle_(); + + this->deep_sleep(); +} +int WaveshareEPaper2P7InB::get_width_internal() { return 176; } +int WaveshareEPaper2P7InB::get_height_internal() { return 264; } +void WaveshareEPaper2P7InB::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.7in B"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// 2.7inch_e-paper_b_v2 +// ======================================================== +// Datasheet: +// - https://www.waveshare.com/w/upload/7/7b/2.7inch-e-paper-b-v2-specification.pdf +// - https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_2in7b_V2.c + +void WaveshareEPaper2P7InBV2::initialize() { + this->reset_(); + + this->wait_until_idle_(); + this->command(0x12); + this->wait_until_idle_(); + + this->command(0x00); + this->data(0x27); + this->data(0x01); + this->data(0x00); + + this->command(0x11); + this->data(0x03); + + // self.SetWindows(0, 0, self.width-1, self.height-1) + // SetWindows(self, Xstart, Ystart, Xend, Yend): + + uint32_t xend = this->get_width_internal() - 1; + uint32_t yend = this->get_height_internal() - 1; + this->command(0x44); + this->data(0x00); + this->data((xend >> 3) & 0xff); + + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data(yend & 0xff); + this->data((yend >> 8) & 0xff); + + // SetCursor(self, Xstart, Ystart): + this->command(0x4E); + this->data(0x00); + this->command(0x4F); + this->data(0x00); + this->data(0x00); +} + +void HOT WaveshareEPaper2P7InBV2::display() { + uint32_t buf_len = this->get_buffer_length_(); + // COMMAND DATA START TRANSMISSION 1 (BLACK) + this->command(0x24); + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i]); + } + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED) + this->command(0x26); + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i]); + } + + delay(2); + + this->command(0x20); + + this->wait_until_idle_(); +} +int WaveshareEPaper2P7InBV2::get_width_internal() { return 176; } +int WaveshareEPaper2P7InBV2::get_height_internal() { return 264; } +void WaveshareEPaper2P7InBV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.7in B V2"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + // ======================================================== // 2.90in Type B (LUT from OTP) // Datasheet: @@ -713,6 +1127,261 @@ void WaveshareEPaper2P9InB::dump_config() { LOG_UPDATE_INTERVAL(this); } +// ======================================================== +// 2.90in Type B (LUT from OTP) +// Datasheet: +// - https://files.waveshare.com/upload/a/af/2.9inch-e-paper-b-v3-specification.pdf +// ======================================================== + +void WaveshareEPaper2P9InBV3::initialize() { + // from https://github.com/waveshareteam/e-Paper/blob/master/Arduino/epd2in9b_V3/epd2in9b_V3.cpp + this->reset_(); + + // COMMAND POWER ON + this->command(0x04); + this->wait_until_idle_(); + + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x0F); + this->data(0x89); + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x80); + this->data(0x01); + this->data(0x28); + + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x77); +} +void HOT WaveshareEPaper2P9InBV3::display() { + // COMMAND DATA START TRANSMISSION 1 (B/W data) + this->command(0x10); + delay(2); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + this->command(0x92); + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED data) + this->command(0x13); + delay(2); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) + this->write_byte(0xFF); + this->end_data_(); + this->command(0x92); + delay(2); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + delay(2); + this->wait_until_idle_(); + + // COMMAND POWER OFF + // NOTE: power off < deep sleep + this->command(0x02); +} +int WaveshareEPaper2P9InBV3::get_width_internal() { return 128; } +int WaveshareEPaper2P9InBV3::get_height_internal() { return 296; } +void WaveshareEPaper2P9InBV3::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.9in (B) V3"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// 2.90in v2 rev2 +// based on SDK and examples in ZIP file from: +// https://www.waveshare.com/pico-epaper-2.9.htm +// ======================================================== + +void WaveshareEPaper2P9InV2R2::initialize() { + this->reset_(); + this->wait_until_idle_(); + + this->command(0x12); // SWRESET + this->wait_until_idle_(); + + this->command(0x01); + this->data(0x27); + this->data(0x01); + this->data(0x00); + + this->command(0x11); + this->data(0x03); + + // SetWindows(0, 0, w, h) + this->command(0x44); + this->data(0x00); + this->data(((this->get_width_controller() - 1) >> 3) & 0xFF); + + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data((this->get_height_internal() - 1) & 0xFF); + this->data(((this->get_height_internal() - 1) >> 8) & 0xFF); + + this->command(0x21); + this->data(0x00); + this->data(0x80); + + // SetCursor(0, 0) + this->command(0x4E); + this->data(0x00); + this->command(0x4f); + this->data(0x00); + this->data(0x00); + + this->wait_until_idle_(); +} + +WaveshareEPaper2P9InV2R2::WaveshareEPaper2P9InV2R2() { this->reset_duration_ = 10; } + +void WaveshareEPaper2P9InV2R2::reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(reset_duration_); // NOLINT + this->reset_pin_->digital_write(true); + delay(reset_duration_); // NOLINT + } +} + +void WaveshareEPaper2P9InV2R2::display() { + if (!this->wait_until_idle_()) { + this->status_set_warning(); + ESP_LOGE(TAG, "fail idle 1"); + return; + } + + if (this->full_update_every_ == 1) { + // do single full update + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // TurnOnDisplay + this->command(0x22); + this->data(0xF7); + this->command(0x20); + return; + } + + // if (this->full_update_every_ == 1 || + if (this->at_update_ == 0) { + // do base update + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + this->command(0x26); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // TurnOnDisplay + this->command(0x22); + this->data(0xF7); + this->command(0x20); + } else { + // do partial update + this->reset_(); + + this->write_lut_(PARTIAL_UPD_2IN9_LUT, PARTIAL_UPD_2IN9_LUT_SIZE); + + this->command(0x37); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x40); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + + this->command(0x3C); + this->data(0x80); + + this->command(0x22); + this->data(0xC0); + this->command(0x20); + + if (!this->wait_until_idle_()) { + ESP_LOGE(TAG, "fail idle 2"); + } + + // SetWindows(0, 0, w, h) + this->command(0x44); + this->data(0x00); + this->data(((this->get_width_controller() - 1) >> 3) & 0xFF); + + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data((this->get_height_internal() - 1) & 0xFF); + this->data(((this->get_height_internal() - 1) >> 8) & 0xFF); + + // SetCursor(0, 0) + this->command(0x4E); + this->data(0x00); + this->command(0x4f); + this->data(0x00); + this->data(0x00); + + // write b/w + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // TurnOnDisplayPartial + this->command(0x22); + this->data(0x0F); + this->command(0x20); + } + + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; +} + +void WaveshareEPaper2P9InV2R2::write_lut_(const uint8_t *lut, const uint8_t size) { + // COMMAND WRITE LUT REGISTER + this->command(0x32); + for (uint8_t i = 0; i < size; i++) + this->data(lut[i]); +} + +void WaveshareEPaper2P9InV2R2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.9inV2R2"); + ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper2P9InV2R2::deep_sleep() { + this->command(0x10); + this->data(0x01); +} + +int WaveshareEPaper2P9InV2R2::get_width_internal() { return 128; } +int WaveshareEPaper2P9InV2R2::get_height_internal() { return 296; } +int WaveshareEPaper2P9InV2R2::get_width_controller() { return this->get_width_internal(); } +void WaveshareEPaper2P9InV2R2::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} + // ======================================================== // Good Display 2.9in black/white/grey // Datasheet: @@ -1321,6 +1990,12 @@ void WaveshareEPaper7P5InBV2::initialize() { // COMMAND TCON SETTING this->command(0x60); this->data(0x22); + + this->command(0x82); + this->data(0x08); + this->command(0x30); + this->data(0x06); + // COMMAND RESOLUTION SETTING this->command(0x65); this->data(0x00); @@ -1350,6 +2025,7 @@ void HOT WaveshareEPaper7P5InBV2::display() { this->command(0x12); delay(100); // NOLINT this->wait_until_idle_(); + this->deep_sleep(); } int WaveshareEPaper7P5InBV2::get_width_internal() { return 800; } int WaveshareEPaper7P5InBV2::get_height_internal() { return 480; } @@ -1495,7 +2171,7 @@ void HOT WaveshareEPaper7P5InBV3::display() { this->command(0x13); // Start Transmission delay(2); for (uint32_t i = 0; i < buf_len; i++) { - this->data(this->buffer_[i]); + this->data(~this->buffer_[i]); } this->command(0x12); // Display Refresh @@ -2089,8 +2765,9 @@ void HOT WaveshareEPaper2P13InDKE::display() { } else { // set up partial update this->command(0x32); - for (uint8_t v : PART_UPDATE_LUT_TTGO_DKE) - this->data(v); + this->start_data_(); + this->write_array(PART_UPDATE_LUT_TTGO_DKE, sizeof(PART_UPDATE_LUT_TTGO_DKE)); + this->end_data_(); this->command(0x3F); this->data(0x22); @@ -2135,12 +2812,10 @@ void HOT WaveshareEPaper2P13InDKE::display() { this->wait_until_idle_(); // data must be sent again on partial update - delay(300); // NOLINT this->command(0x24); this->start_data_(); this->write_array(this->buffer_, this->get_buffer_length_()); this->end_data_(); - delay(300); // NOLINT } ESP_LOGI(TAG, "Completed e-paper update."); @@ -2152,6 +2827,7 @@ uint32_t WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; } void WaveshareEPaper2P13InDKE::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); ESP_LOGCONFIG(TAG, " Model: 2.13inDKE"); + LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 42e8a16829..ffc099ca56 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -7,9 +7,9 @@ namespace esphome { namespace waveshare_epaper { -class WaveshareEPaper : public display::DisplayBuffer, - public spi::SPIDevice { +class WaveshareEPaperBase : public display::DisplayBuffer, + public spi::SPIDevice { public: void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } float get_setup_priority() const override; @@ -19,6 +19,7 @@ class WaveshareEPaper : public display::DisplayBuffer, void command(uint8_t value); void data(uint8_t value); + void cmd_data(const uint8_t *data, size_t length); virtual void display() = 0; virtual void initialize() = 0; @@ -26,8 +27,6 @@ class WaveshareEPaper : public display::DisplayBuffer, void update() override; - void fill(Color color) override; - void setup() override { this->setup_pins_(); this->initialize(); @@ -35,11 +34,7 @@ class WaveshareEPaper : public display::DisplayBuffer, void on_safe_shutdown() override; - display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; } - protected: - void draw_absolute_pixel_internal(int x, int y, Color color) override; - bool wait_until_idle_(); void setup_pins_(); @@ -49,13 +44,13 @@ class WaveshareEPaper : public display::DisplayBuffer, this->reset_pin_->digital_write(false); delay(reset_duration_); // NOLINT this->reset_pin_->digital_write(true); - delay(200); // NOLINT + delay(20); } } virtual int get_width_controller() { return this->get_width_internal(); }; - uint32_t get_buffer_length_(); + virtual uint32_t get_buffer_length_() = 0; // NOLINT(readability-identifier-naming) uint32_t reset_duration_{200}; void start_command_(); @@ -69,10 +64,33 @@ class WaveshareEPaper : public display::DisplayBuffer, virtual uint32_t idle_timeout_() { return 1000u; } // NOLINT(readability-identifier-naming) }; +class WaveshareEPaper : public WaveshareEPaperBase { + public: + void fill(Color color) override; + + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; } + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + uint32_t get_buffer_length_() override; +}; + +class WaveshareEPaperBWR : public WaveshareEPaperBase { + public: + void fill(Color color) override; + + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + uint32_t get_buffer_length_() override; +}; + enum WaveshareEPaperTypeAModel { WAVESHARE_EPAPER_1_54_IN = 0, WAVESHARE_EPAPER_1_54_IN_V2, WAVESHARE_EPAPER_2_13_IN, + WAVESHARE_EPAPER_2_13_IN_V2, WAVESHARE_EPAPER_2_9_IN, WAVESHARE_EPAPER_2_9_IN_V2, TTGO_EPAPER_2_13_IN, @@ -97,6 +115,7 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { case WAVESHARE_EPAPER_1_54_IN: case WAVESHARE_EPAPER_1_54_IN_V2: case WAVESHARE_EPAPER_2_9_IN_V2: + case WAVESHARE_EPAPER_2_13_IN_V2: // COMMAND DEEP SLEEP MODE this->command(0x10); this->data(0x01); @@ -107,7 +126,11 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { this->command(0x10); break; } - this->wait_until_idle_(); + if (this->model_ != WAVESHARE_EPAPER_2_13_IN_V2) { + // From panel specification: + // "After this command initiated, the chip will enter Deep Sleep Mode, BUSY pad will keep output high." + this->wait_until_idle_(); + } } void set_full_update_every(uint32_t full_update_every); @@ -133,6 +156,8 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { enum WaveshareEPaperTypeBModel { WAVESHARE_EPAPER_2_7_IN = 0, + WAVESHARE_EPAPER_2_7_IN_B, + WAVESHARE_EPAPER_2_7_IN_B_V2, WAVESHARE_EPAPER_4_2_IN, WAVESHARE_EPAPER_4_2_IN_B_V2, WAVESHARE_EPAPER_7_5_IN, @@ -156,7 +181,49 @@ class WaveshareEPaper2P7In : public WaveshareEPaper { protected: int get_width_internal() override; + int get_height_internal() override; +}; +class WaveshareEPaper2P7InB : public WaveshareEPaperBWR { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND VCOM_AND_DATA_INTERVAL_SETTING + this->command(0x50); + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); // deep sleep + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + int get_height_internal() override; +}; + +class WaveshareEPaper2P7InBV2 : public WaveshareEPaperBWR { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND DEEP SLEEP + this->command(0x10); + this->data(0x01); + } + + protected: + int get_width_internal() override; int get_height_internal() override; }; @@ -180,6 +247,22 @@ class GDEY029T94 : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper2P7InV2 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { ; } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + class GDEW0154M09 : public WaveshareEPaper { public: void initialize() override; @@ -240,6 +323,56 @@ class WaveshareEPaper2P9InB : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper2P9InBV3 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper2P9InV2R2 : public WaveshareEPaper { + public: + WaveshareEPaper2P9InV2R2(); + + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override; + + void set_full_update_every(uint32_t full_update_every); + + protected: + void write_lut_(const uint8_t *lut, uint8_t size); + + int get_width_internal() override; + + int get_height_internal() override; + + int get_width_controller() override; + + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + + private: + void reset_(); +}; + class WaveshareEPaper4P2In : public WaveshareEPaper { public: void initialize() override; @@ -578,5 +711,39 @@ class WaveshareEPaper2P13InDKE : public WaveshareEPaper { uint32_t at_update_{0}; }; +class WaveshareEPaper2P13InV3 : public WaveshareEPaper { + public: + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER DOWN + this->command(0x10); + this->data(0x01); + // cannot wait until idle here, the device no longer responds + } + + void set_full_update_every(uint32_t full_update_every); + + void setup() override; + void initialize() override; + + protected: + int get_width_internal() override; + int get_height_internal() override; + uint32_t idle_timeout_() override; + + void write_buffer_(uint8_t cmd, int top, int bottom); + void set_window_(int t, int b); + void send_reset_(); + void partial_update_(); + void full_update_(); + + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + bool is_busy_{false}; + void write_lut_(const uint8_t *lut); +}; } // namespace waveshare_epaper } // namespace esphome diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 2708b5d06e..bbd5bc662e 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -44,6 +44,11 @@ def default_url(config): config[CONF_CSS_URL] = "" if not (CONF_JS_URL in config): config[CONF_JS_URL] = "https://oi.esphome.io/v2/www.js" + if config[CONF_VERSION] == 3: + if not (CONF_CSS_URL in config): + config[CONF_CSS_URL] = "" + if not (CONF_JS_URL in config): + config[CONF_JS_URL] = "https://oi.esphome.io/v3/www.js" return config @@ -64,7 +69,7 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(WebServer), cv.Optional(CONF_PORT, default=80): cv.port, - cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True), + cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, 3, int=True), cv.Optional(CONF_CSS_URL): cv.string, cv.Optional(CONF_CSS_INCLUDE): cv.file_, cv.Optional(CONF_JS_URL): cv.string, @@ -152,7 +157,7 @@ async def to_code(config): cg.add_define("USE_WEBSERVER") cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT]) cg.add_define("USE_WEBSERVER_VERSION", version) - if version == 2: + if version >= 2: # Don't compress the index HTML as the data sizes are almost the same. add_resource_as_progmem("INDEX_HTML", build_index_html(config), compress=False) else: diff --git a/esphome/components/web_server/list_entities.cpp b/esphome/components/web_server/list_entities.cpp index 5c9009c5da..2252f55008 100644 --- a/esphome/components/web_server/list_entities.cpp +++ b/esphome/components/web_server/list_entities.cpp @@ -12,6 +12,8 @@ ListEntitiesIterator::ListEntitiesIterator(WebServer *web_server) : web_server_( #ifdef USE_BINARY_SENSOR bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send( this->web_server_->binary_sensor_json(binary_sensor, binary_sensor->state, DETAIL_ALL).c_str(), "state"); return true; @@ -19,30 +21,40 @@ bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_ #endif #ifdef USE_COVER bool ListEntitiesIterator::on_cover(cover::Cover *cover) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->cover_json(cover, DETAIL_ALL).c_str(), "state"); return true; } #endif #ifdef USE_FAN bool ListEntitiesIterator::on_fan(fan::Fan *fan) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->fan_json(fan, DETAIL_ALL).c_str(), "state"); return true; } #endif #ifdef USE_LIGHT bool ListEntitiesIterator::on_light(light::LightState *light) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->light_json(light, DETAIL_ALL).c_str(), "state"); return true; } #endif #ifdef USE_SENSOR bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->sensor_json(sensor, sensor->state, DETAIL_ALL).c_str(), "state"); return true; } #endif #ifdef USE_SWITCH bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->switch_json(a_switch, a_switch->state, DETAIL_ALL).c_str(), "state"); return true; @@ -50,12 +62,16 @@ bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { #endif #ifdef USE_BUTTON bool ListEntitiesIterator::on_button(button::Button *button) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->button_json(button, DETAIL_ALL).c_str(), "state"); return true; } #endif #ifdef USE_TEXT_SENSOR bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send( this->web_server_->text_sensor_json(text_sensor, text_sensor->state, DETAIL_ALL).c_str(), "state"); return true; @@ -63,6 +79,8 @@ bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) #endif #ifdef USE_LOCK bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->lock_json(a_lock, a_lock->state, DETAIL_ALL).c_str(), "state"); return true; } @@ -70,6 +88,8 @@ bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { #ifdef USE_CLIMATE bool ListEntitiesIterator::on_climate(climate::Climate *climate) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->climate_json(climate, DETAIL_ALL).c_str(), "state"); return true; } @@ -77,13 +97,26 @@ bool ListEntitiesIterator::on_climate(climate::Climate *climate) { #ifdef USE_NUMBER bool ListEntitiesIterator::on_number(number::Number *number) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->number_json(number, number->state, DETAIL_ALL).c_str(), "state"); return true; } #endif +#ifdef USE_DATETIME_DATE +bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { + if (this->web_server_->events_.count() == 0) + return true; + this->web_server_->events_.send(this->web_server_->date_json(date, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + #ifdef USE_TEXT bool ListEntitiesIterator::on_text(text::Text *text) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->text_json(text, text->state, DETAIL_ALL).c_str(), "state"); return true; } @@ -91,6 +124,8 @@ bool ListEntitiesIterator::on_text(text::Text *text) { #ifdef USE_SELECT bool ListEntitiesIterator::on_select(select::Select *select) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->select_json(select, select->state, DETAIL_ALL).c_str(), "state"); return true; } @@ -98,6 +133,8 @@ bool ListEntitiesIterator::on_select(select::Select *select) { #ifdef USE_ALARM_CONTROL_PANEL bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send( this->web_server_->alarm_control_panel_json(a_alarm_control_panel, a_alarm_control_panel->get_state(), DETAIL_ALL) .c_str(), diff --git a/esphome/components/web_server/list_entities.h b/esphome/components/web_server/list_entities.h index 7da5b3fe2c..cd7c9099d6 100644 --- a/esphome/components/web_server/list_entities.h +++ b/esphome/components/web_server/list_entities.h @@ -41,6 +41,9 @@ class ListEntitiesIterator : public ComponentIterator { #ifdef USE_NUMBER bool on_number(number::Number *number) override; #endif +#ifdef USE_DATETIME_DATE + bool on_date(datetime::DateEntity *date) override; +#endif #ifdef USE_TEXT bool on_text(text::Text *text) override; #endif diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 0d72e274cd..6c3e4e5eec 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -4,6 +4,7 @@ #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/util.h" @@ -357,7 +358,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream->print(F("")); request->send(stream); } -#elif USE_WEBSERVER_VERSION == 2 +#elif USE_WEBSERVER_VERSION >= 2 void WebServer::handle_index_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", ESPHOME_WEBSERVER_INDEX_HTML, ESPHOME_WEBSERVER_INDEX_HTML_SIZE); @@ -397,22 +398,26 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) { #define set_json_id(root, obj, sensor, start_config) \ (root)["id"] = sensor; \ - if (((start_config) == DETAIL_ALL)) \ - (root)["name"] = (obj)->get_name(); + if (((start_config) == DETAIL_ALL)) { \ + (root)["name"] = (obj)->get_name(); \ + (root)["icon"] = (obj)->get_icon(); \ + (root)["entity_category"] = (obj)->get_entity_category(); \ + if ((obj)->is_disabled_by_default()) \ + (root)["is_disabled_by_default"] = (obj)->is_disabled_by_default(); \ + } #define set_json_value(root, obj, sensor, value, start_config) \ - set_json_id((root), (obj), sensor, start_config)(root)["value"] = value; - -#define set_json_state_value(root, obj, sensor, state, value, start_config) \ - set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state; + set_json_id((root), (obj), sensor, start_config); \ + (root)["value"] = value; #define set_json_icon_state_value(root, obj, sensor, state, value, start_config) \ - set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state; \ - if (((start_config) == DETAIL_ALL)) \ - (root)["icon"] = (obj)->get_icon(); + set_json_value(root, obj, sensor, value, start_config); \ + (root)["state"] = state; #ifdef USE_SENSOR void WebServer::on_sensor_update(sensor::Sensor *obj, float state) { + if (this->events_.count() == 0) + return; this->events_.send(this->sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -436,12 +441,18 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail state += " " + obj->get_unit_of_measurement(); } set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config); + if (start_config == DETAIL_ALL) { + if (!obj->get_unit_of_measurement().empty()) + root["uom"] = obj->get_unit_of_measurement(); + } }); } #endif #ifdef USE_TEXT_SENSOR void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) { + if (this->events_.count() == 0) + return; this->events_.send(this->text_sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -464,6 +475,8 @@ std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std: #ifdef USE_SWITCH void WebServer::on_switch_update(switch_::Switch *obj, bool state) { + if (this->events_.count() == 0) + return; this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state"); } std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) { @@ -479,7 +492,7 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->switch_json(obj, obj->state, DETAIL_STATE); request->send(200, "application/json", data.c_str()); } else if (match.method == "toggle") { @@ -510,7 +523,7 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM for (button::Button *obj : App.get_buttons()) { if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_POST && match.method == "press") { + if (match.method == "press") { this->schedule_([obj]() { obj->press(); }); request->send(200); return; @@ -525,11 +538,14 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM #ifdef USE_BINARY_SENSOR void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { + if (this->events_.count() == 0) + return; this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); } std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { return json::build_json([obj, value, start_config](JsonObject root) { - set_json_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); + set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, + start_config); }); } void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -545,10 +561,15 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con #endif #ifdef USE_FAN -void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state"); } +void WebServer::on_fan_update(fan::Fan *obj) { + if (this->events_.count() == 0) + return; + this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state"); +} std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { return json::build_json([obj, start_config](JsonObject root) { - set_json_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, start_config); + set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, + start_config); const auto traits = obj->get_traits(); if (traits.supports_speed()) { root["speed_level"] = obj->speed; @@ -563,7 +584,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->fan_json(obj, DETAIL_STATE); request->send(200, "application/json", data.c_str()); } else if (match.method == "toggle") { @@ -614,6 +635,8 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc #ifdef USE_LIGHT void WebServer::on_light_update(light::LightState *obj) { + if (this->events_.count() == 0) + return; this->events_.send(this->light_json(obj, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -621,7 +644,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->light_json(obj, DETAIL_STATE); request->send(200, "application/json", data.c_str()); } else if (match.method == "toggle") { @@ -720,6 +743,8 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi #ifdef USE_COVER void WebServer::on_cover_update(cover::Cover *obj) { + if (this->events_.count() == 0) + return; this->events_.send(this->cover_json(obj, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -727,7 +752,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->cover_json(obj, DETAIL_STATE); request->send(200, "application/json", data.c_str()); continue; @@ -740,6 +765,8 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa call.set_command_close(); } else if (match.method == "stop") { call.set_command_stop(); + } else if (match.method == "toggle") { + call.set_command_toggle(); } else if (match.method != "set") { request->send(404); return; @@ -773,10 +800,12 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa } std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { return json::build_json([obj, start_config](JsonObject root) { - set_json_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", - obj->position, start_config); + set_json_icon_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", + obj->position, start_config); root["current_operation"] = cover::cover_operation_to_str(obj->current_operation); + if (obj->get_traits().get_supports_position()) + root["position"] = obj->position; if (obj->get_traits().get_supports_tilt()) root["tilt"] = obj->tilt; }); @@ -785,6 +814,8 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { #ifdef USE_NUMBER void WebServer::on_number_update(number::Number *obj, float state) { + if (this->events_.count() == 0) + return; this->events_.send(this->number_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -792,7 +823,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->number_json(obj, obj->state, DETAIL_STATE); request->send(200, "application/json", data.c_str()); return; @@ -824,6 +855,8 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail root["max_value"] = obj->traits.get_max_value(); root["step"] = obj->traits.get_step(); root["mode"] = (int) obj->traits.get_mode(); + if (!obj->traits.get_unit_of_measurement().empty()) + root["uom"] = obj->traits.get_unit_of_measurement(); } if (std::isnan(value)) { root["value"] = "\"NaN\""; @@ -839,8 +872,59 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail } #endif +#ifdef USE_DATETIME_DATE +void WebServer::on_date_update(datetime::DateEntity *obj) { + if (this->events_.count() == 0) + return; + this->events_.send(this->date_json(obj, DETAIL_STATE).c_str(), "state"); +} +void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (auto *obj : App.get_dates()) { + if (obj->get_object_id() != match.id) + continue; + if (request->method() == HTTP_GET) { + std::string data = this->date_json(obj, DETAIL_STATE); + request->send(200, "application/json", data.c_str()); + return; + } + if (match.method != "set") { + request->send(404); + return; + } + + auto call = obj->make_call(); + + if (!request->hasParam("value")) { + request->send(409); + return; + } + + if (request->hasParam("value")) { + std::string value = request->getParam("value")->value().c_str(); + call.set_date(value); + } + + this->schedule_([call]() mutable { call.perform(); }); + request->send(200); + return; + } + request->send(404); +} + +std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_config) { + return json::build_json([obj, start_config](JsonObject root) { + set_json_id(root, obj, "date-" + obj->get_object_id(), start_config); + std::string value = str_sprintf("%d-%02d-%02d", obj->year, obj->month, obj->day); + root["value"] = value; + root["state"] = value; + }); +} +#endif // USE_DATETIME_DATE + #ifdef USE_TEXT void WebServer::on_text_update(text::Text *obj, const std::string &state) { + if (this->events_.count() == 0) + return; this->events_.send(this->text_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -848,7 +932,7 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->text_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); return; @@ -892,6 +976,8 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json #ifdef USE_SELECT void WebServer::on_select_update(select::Select *obj, const std::string &state, size_t index) { + if (this->events_.count() == 0) + return; this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -899,7 +985,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { auto detail = DETAIL_STATE; auto *param = request->getParam("detail"); if (param && param->value() == "all") { @@ -930,7 +1016,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM } std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) { return json::build_json([obj, value, start_config](JsonObject root) { - set_json_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config); + set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config); if (start_config == DETAIL_ALL) { JsonArray opt = root.createNestedArray("option"); for (auto &option : obj->traits.get_options()) { @@ -946,6 +1032,8 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value #ifdef USE_CLIMATE void WebServer::on_climate_update(climate::Climate *obj) { + if (this->events_.count() == 0) + return; this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state"); } @@ -954,7 +1042,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->climate_json(obj, DETAIL_STATE); request->send(200, "application/json", data.c_str()); return; @@ -1087,6 +1175,8 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf #ifdef USE_LOCK void WebServer::on_lock_update(lock::Lock *obj) { + if (this->events_.count() == 0) + return; this->events_.send(this->lock_json(obj, obj->state, DETAIL_STATE).c_str(), "state"); } std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { @@ -1100,7 +1190,7 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->lock_json(obj, obj->state, DETAIL_STATE); request->send(200, "application/json", data.c_str()); } else if (match.method == "lock") { @@ -1123,6 +1213,8 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat #ifdef USE_ALARM_CONTROL_PANEL void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) { + if (this->events_.count() == 0) + return; this->events_.send(this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE).c_str(), "state"); } std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj, @@ -1139,7 +1231,7 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE); request->send(200, "application/json", data.c_str()); return; @@ -1189,7 +1281,7 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { #endif #ifdef USE_BUTTON - if (request->method() == HTTP_POST && match.domain == "button") + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "button") return true; #endif @@ -1223,6 +1315,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_DATETIME_DATE + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "date") + return true; +#endif + #ifdef USE_TEXT if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "text") return true; @@ -1341,6 +1438,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { } #endif +#ifdef USE_DATETIME_DATE + if (match.domain == "date") { + this->handle_date_request(request, match); + return; + } +#endif + #ifdef USE_TEXT if (match.domain == "text") { this->handle_text_request(request, match); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 465e231984..57cbbe1339 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -13,7 +13,7 @@ #include #endif -#if USE_WEBSERVER_VERSION == 2 +#if USE_WEBSERVER_VERSION >= 2 extern const uint8_t ESPHOME_WEBSERVER_INDEX_HTML[] PROGMEM; extern const size_t ESPHOME_WEBSERVER_INDEX_HTML_SIZE; #endif @@ -221,6 +221,15 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { std::string number_json(number::Number *obj, float value, JsonDetail start_config); #endif +#ifdef USE_DATETIME_DATE + void on_date_update(datetime::DateEntity *obj) override; + /// Handle a date request under '/date/'. + void handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match); + + /// Dump the date state with its value as a JSON string. + std::string date_json(datetime::DateEntity *obj, JsonDetail start_config); +#endif + #ifdef USE_TEXT void on_text_update(text::Text *obj, const std::string &state) override; /// Handle a text input request under '/text/'. diff --git a/esphome/components/web_server_idf/utils.cpp b/esphome/components/web_server_idf/utils.cpp new file mode 100644 index 0000000000..6299937ce1 --- /dev/null +++ b/esphome/components/web_server_idf/utils.cpp @@ -0,0 +1,93 @@ +#ifdef USE_ESP_IDF +#include +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "http_parser.h" + +#include "utils.h" + +namespace esphome { +namespace web_server_idf { + +static const char *const TAG = "web_server_idf_utils"; + +void url_decode(char *str) { + char *ptr = str, buf; + for (; *str; str++, ptr++) { + if (*str == '%') { + str++; + if (parse_hex(str, 2, reinterpret_cast(&buf), 1) == 2) { + *ptr = buf; + str++; + } else { + str--; + *ptr = *str; + } + } else if (*str == '+') { + *ptr = ' '; + } else { + *ptr = *str; + } + } + *ptr = *str; +} + +bool request_has_header(httpd_req_t *req, const char *name) { return httpd_req_get_hdr_value_len(req, name); } + +optional request_get_header(httpd_req_t *req, const char *name) { + size_t len = httpd_req_get_hdr_value_len(req, name); + if (len == 0) { + return {}; + } + + std::string str; + str.resize(len); + + auto res = httpd_req_get_hdr_value_str(req, name, &str[0], len + 1); + if (res != ESP_OK) { + return {}; + } + + return {str}; +} + +optional request_get_url_query(httpd_req_t *req) { + auto len = httpd_req_get_url_query_len(req); + if (len == 0) { + return {}; + } + + std::string str; + str.resize(len); + + auto res = httpd_req_get_url_query_str(req, &str[0], len + 1); + if (res != ESP_OK) { + ESP_LOGW(TAG, "Can't get query for request: %s", esp_err_to_name(res)); + return {}; + } + + return {str}; +} + +optional query_key_value(const std::string &query_url, const std::string &key) { + if (query_url.empty()) { + return {}; + } + + auto val = std::unique_ptr(new char[query_url.size()]); + if (!val) { + ESP_LOGE(TAG, "Not enough memory to the query key value"); + return {}; + } + + if (httpd_query_key_value(query_url.c_str(), key.c_str(), val.get(), query_url.size()) != ESP_OK) { + return {}; + } + + url_decode(val.get()); + return {val.get()}; +} + +} // namespace web_server_idf +} // namespace esphome +#endif // USE_ESP_IDF diff --git a/esphome/components/web_server_idf/utils.h b/esphome/components/web_server_idf/utils.h new file mode 100644 index 0000000000..9ed17c1d50 --- /dev/null +++ b/esphome/components/web_server_idf/utils.h @@ -0,0 +1,17 @@ +#pragma once +#ifdef USE_ESP_IDF + +#include +#include "esphome/core/helpers.h" + +namespace esphome { +namespace web_server_idf { + +bool request_has_header(httpd_req_t *req, const char *name); +optional request_get_header(httpd_req_t *req, const char *name); +optional request_get_url_query(httpd_req_t *req); +optional query_key_value(const std::string &query_url, const std::string &key); + +} // namespace web_server_idf +} // namespace esphome +#endif // USE_ESP_IDF diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 8e67f3f169..cf187cd647 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -7,6 +7,7 @@ #include "esp_tls_crypto.h" +#include "utils.h" #include "web_server_idf.h" namespace esphome { @@ -47,7 +48,7 @@ void AsyncWebServer::begin() { const httpd_uri_t handler_post = { .uri = "", .method = HTTP_POST, - .handler = AsyncWebServer::request_handler, + .handler = AsyncWebServer::request_post_handler, .user_ctx = this, }; httpd_register_uri_handler(this->server_, &handler_post); @@ -62,20 +63,62 @@ void AsyncWebServer::begin() { } } +esp_err_t AsyncWebServer::request_post_handler(httpd_req_t *r) { + ESP_LOGVV(TAG, "Enter AsyncWebServer::request_post_handler. uri=%s", r->uri); + auto content_type = request_get_header(r, "Content-Type"); + if (content_type.has_value() && *content_type != "application/x-www-form-urlencoded") { + ESP_LOGW(TAG, "Only application/x-www-form-urlencoded supported for POST request"); + // fallback to get handler to support backward compatibility + return AsyncWebServer::request_handler(r); + } + + if (!request_has_header(r, "Content-Length")) { + ESP_LOGW(TAG, "Content length is requred for post: %s", r->uri); + httpd_resp_send_err(r, HTTPD_411_LENGTH_REQUIRED, nullptr); + return ESP_OK; + } + + if (r->content_len > HTTPD_MAX_REQ_HDR_LEN) { + ESP_LOGW(TAG, "Request size is to big: %zu", r->content_len); + httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr); + return ESP_FAIL; + } + + std::string post_query; + if (r->content_len > 0) { + post_query.resize(r->content_len); + const int ret = httpd_req_recv(r, &post_query[0], r->content_len + 1); + if (ret <= 0) { // 0 return value indicates connection closed + if (ret == HTTPD_SOCK_ERR_TIMEOUT) { + httpd_resp_send_err(r, HTTPD_408_REQ_TIMEOUT, nullptr); + return ESP_ERR_TIMEOUT; + } + httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr); + return ESP_FAIL; + } + } + + AsyncWebServerRequest req(r, std::move(post_query)); + return static_cast(r->user_ctx)->request_handler_(&req); +} + esp_err_t AsyncWebServer::request_handler(httpd_req_t *r) { - ESP_LOGV(TAG, "Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri); + ESP_LOGVV(TAG, "Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri); AsyncWebServerRequest req(r); - auto *server = static_cast(r->user_ctx); - for (auto *handler : server->handlers_) { - if (handler->canHandle(&req)) { + return static_cast(r->user_ctx)->request_handler_(&req); +} + +esp_err_t AsyncWebServer::request_handler_(AsyncWebServerRequest *request) const { + for (auto *handler : this->handlers_) { + if (handler->canHandle(request)) { // At now process only basic requests. // OTA requires multipart request support and handleUpload for it - handler->handleRequest(&req); + handler->handleRequest(request); return ESP_OK; } } - if (server->on_not_found_) { - server->on_not_found_(&req); + if (this->on_not_found_) { + this->on_not_found_(request); return ESP_OK; } return ESP_ERR_NOT_FOUND; @@ -88,22 +131,10 @@ AsyncWebServerRequest::~AsyncWebServerRequest() { } } -bool AsyncWebServerRequest::hasHeader(const char *name) const { return httpd_req_get_hdr_value_len(*this, name); } +bool AsyncWebServerRequest::hasHeader(const char *name) const { return request_has_header(*this, name); } optional AsyncWebServerRequest::get_header(const char *name) const { - size_t buf_len = httpd_req_get_hdr_value_len(*this, name); - if (buf_len == 0) { - return {}; - } - auto buf = std::unique_ptr(new char[++buf_len]); - if (!buf) { - ESP_LOGE(TAG, "No enough memory for get header %s", name); - return {}; - } - if (httpd_req_get_hdr_value_str(*this, name, buf.get(), buf_len) != ESP_OK) { - return {}; - } - return {buf.get()}; + return request_get_header(*this, name); } std::string AsyncWebServerRequest::url() const { @@ -193,74 +224,25 @@ void AsyncWebServerRequest::requestAuthentication(const char *realm) const { httpd_resp_send_err(*this, HTTPD_401_UNAUTHORIZED, nullptr); } -static std::string url_decode(const std::string &in) { - std::string out; - out.reserve(in.size()); - for (std::size_t i = 0; i < in.size(); ++i) { - if (in[i] == '%') { - ++i; - if (i + 1 < in.size()) { - auto c = parse_hex(&in[i], 2); - if (c.has_value()) { - out += static_cast(*c); - ++i; - } else { - out += '%'; - out += in[i++]; - out += in[i]; - } - } else { - out += '%'; - out += in[i]; - } - } else if (in[i] == '+') { - out += ' '; - } else { - out += in[i]; - } - } - return out; -} - AsyncWebParameter *AsyncWebServerRequest::getParam(const std::string &name) { auto find = this->params_.find(name); if (find != this->params_.end()) { return find->second; } - auto query_len = httpd_req_get_url_query_len(this->req_); - if (query_len == 0) { - return nullptr; + optional val = query_key_value(this->post_query_, name); + if (!val.has_value()) { + auto url_query = request_get_url_query(*this); + if (url_query.has_value()) { + val = query_key_value(url_query.value(), name); + } } - auto query_str = std::unique_ptr(new char[++query_len]); - if (!query_str) { - ESP_LOGE(TAG, "No enough memory for get query param"); - return nullptr; + AsyncWebParameter *param = nullptr; + if (val.has_value()) { + param = new AsyncWebParameter(val.value()); // NOLINT(cppcoreguidelines-owning-memory) } - - auto res = httpd_req_get_url_query_str(*this, query_str.get(), query_len); - if (res != ESP_OK) { - ESP_LOGW(TAG, "Can't get query for request: %s", esp_err_to_name(res)); - return nullptr; - } - - auto query_val = std::unique_ptr(new char[query_len]); - if (!query_val) { - ESP_LOGE(TAG, "No enough memory for get query param value"); - return nullptr; - } - - res = httpd_query_key_value(query_str.get(), name.c_str(), query_val.get(), query_len); - if (res != ESP_OK) { - this->params_.insert({name, nullptr}); - return nullptr; - } - query_str.release(); - auto decoded = url_decode(query_val.get()); - query_val.release(); - auto *param = new AsyncWebParameter(decoded); // NOLINT(cppcoreguidelines-owning-memory) - this->params_.insert(std::make_pair(name, param)); + this->params_.insert({name, param}); return param; } @@ -271,14 +253,15 @@ void AsyncWebServerResponse::addHeader(const char *name, const char *value) { void AsyncResponseStream::print(float value) { this->print(to_string(value)); } void AsyncResponseStream::printf(const char *fmt, ...) { - std::string str; va_list args; va_start(args, fmt); - size_t length = vsnprintf(nullptr, 0, fmt, args); + const int length = vsnprintf(nullptr, 0, fmt, args); va_end(args); + std::string str; str.resize(length); + va_start(args, fmt); vsnprintf(&str[0], length + 1, fmt, args); va_end(args); diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index 2fbc5cd2e9..2ead5e3f03 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -3,11 +3,11 @@ #include -#include #include -#include #include #include +#include +#include namespace esphome { namespace web_server_idf { @@ -90,11 +90,10 @@ class AsyncWebServerResponseProgmem : public AsyncWebServerResponse { protected: const uint8_t *data_; - const size_t size_; + size_t size_; }; class AsyncWebServerRequest { - // FIXME friend class AsyncWebServerResponse; friend class AsyncWebServer; public: @@ -164,7 +163,9 @@ class AsyncWebServerRequest { httpd_req_t *req_; AsyncWebServerResponse *rsp_{}; std::map params_; + std::string post_query_; AsyncWebServerRequest(httpd_req_t *req) : req_(req) {} + AsyncWebServerRequest(httpd_req_t *req, std::string post_query) : req_(req), post_query_(std::move(post_query)) {} void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type); }; @@ -191,6 +192,8 @@ class AsyncWebServer { uint16_t port_{}; httpd_handle_t server_{}; static esp_err_t request_handler(httpd_req_t *r); + static esp_err_t request_post_handler(httpd_req_t *r); + esp_err_t request_handler_(AsyncWebServerRequest *request) const; std::vector handlers_; std::function on_not_found_{}; }; @@ -248,6 +251,8 @@ class AsyncEventSource : public AsyncWebHandler { void send(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0); + size_t count() const { return this->sessions_.size(); } + protected: std::string url_; std::set sessions_; diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 519489097a..8f46bf29a0 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -55,6 +55,9 @@ void WiFiComponent::start() { uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time()) : 88491487UL; this->pref_ = global_preferences->make_preference(hash, true); + if (this->fast_connect_) { + this->fast_connect_pref_ = global_preferences->make_preference(hash, false); + } SavedWifiSettings save{}; if (this->pref_.load(&save)) { @@ -78,6 +81,7 @@ void WiFiComponent::start() { if (this->fast_connect_) { this->selected_ap_ = this->sta_[0]; + this->load_fast_connect_settings_(); this->start_connecting(this->selected_ap_, false); } else { this->start_scanning(); @@ -203,14 +207,13 @@ void WiFiComponent::set_fast_connect(bool fast_connect) { this->fast_connect_ = void WiFiComponent::set_btm(bool btm) { this->btm_ = btm; } void WiFiComponent::set_rrm(bool rrm) { this->rrm_ = rrm; } #endif - -network::IPAddress WiFiComponent::get_ip_address() { +network::IPAddresses WiFiComponent::get_ip_addresses() { if (this->has_sta()) - return this->wifi_sta_ip(); + return this->wifi_sta_ip_addresses(); #ifdef USE_WIFI_AP if (this->has_ap()) - return this->wifi_soft_ap_ip(); + return {this->wifi_soft_ap_ip()}; #endif // USE_WIFI_AP return {}; @@ -408,7 +411,11 @@ void WiFiComponent::print_connect_params_() { return; } ESP_LOGCONFIG(TAG, " SSID: " LOG_SECRET("'%s'"), wifi_ssid().c_str()); - ESP_LOGCONFIG(TAG, " IP Address: %s", wifi_sta_ip().str().c_str()); + for (auto &ip : wifi_sta_ip_addresses()) { + if (ip.is_set()) { + ESP_LOGCONFIG(TAG, " IP Address: %s", ip.str().c_str()); + } + } ESP_LOGCONFIG(TAG, " BSSID: " LOG_SECRET("%02X:%02X:%02X:%02X:%02X:%02X"), bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); ESP_LOGCONFIG(TAG, " Hostname: '%s'", App.get_name().c_str()); @@ -604,6 +611,11 @@ void WiFiComponent::check_connecting_finished() { this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED; this->num_retried_ = 0; + + if (this->fast_connect_) { + this->save_fast_connect_settings_(); + } + return; } @@ -649,12 +661,19 @@ void WiFiComponent::retry_connect() { delay(10); if (!this->is_captive_portal_active_() && !this->is_esp32_improv_active_() && - (this->num_retried_ > 5 || this->error_from_callback_)) { - // If retry failed for more than 5 times, let's restart STA - ESP_LOGW(TAG, "Restarting WiFi adapter..."); - this->wifi_mode_(false, {}); - delay(100); // NOLINT - this->num_retried_ = 0; + (this->num_retried_ > 3 || this->error_from_callback_)) { + if (this->num_retried_ > 5) { + // If retry failed for more than 5 times, let's restart STA + ESP_LOGW(TAG, "Restarting WiFi adapter..."); + this->wifi_mode_(false, {}); + delay(100); // NOLINT + this->num_retried_ = 0; + } else { + // Try hidden networks after 3 failed retries + ESP_LOGD(TAG, "Retrying with hidden networks..."); + this->fast_connect_ = true; + this->num_retried_++; + } } else { this->num_retried_++; } @@ -705,6 +724,35 @@ bool WiFiComponent::is_esp32_improv_active_() { #endif } +void WiFiComponent::load_fast_connect_settings_() { + SavedWifiFastConnectSettings fast_connect_save{}; + + if (this->fast_connect_pref_.load(&fast_connect_save)) { + bssid_t bssid{}; + std::copy(fast_connect_save.bssid, fast_connect_save.bssid + 6, bssid.begin()); + this->selected_ap_.set_bssid(bssid); + this->selected_ap_.set_channel(fast_connect_save.channel); + + ESP_LOGD(TAG, "Loaded saved fast_connect wifi settings"); + } +} + +void WiFiComponent::save_fast_connect_settings_() { + bssid_t bssid = wifi_bssid(); + uint8_t channel = wifi_channel_(); + + if (bssid != this->selected_ap_.get_bssid() || channel != this->selected_ap_.get_channel()) { + SavedWifiFastConnectSettings fast_connect_save{}; + + memcpy(fast_connect_save.bssid, bssid.data(), 6); + fast_connect_save.channel = channel; + + this->fast_connect_pref_.save(&fast_connect_save); + + ESP_LOGD(TAG, "Saved fast_connect wifi settings"); + } +} + void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = ssid; } void WiFiAP::set_bssid(bssid_t bssid) { this->bssid_ = bssid; } void WiFiAP::set_bssid(optional bssid) { this->bssid_ = bssid; } diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 6cbdc51caf..d9cb6a9ae2 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -48,6 +48,11 @@ struct SavedWifiSettings { char password[65]; } PACKED; // NOLINT +struct SavedWifiFastConnectSettings { + uint8_t bssid[6]; + uint8_t channel; +} PACKED; // NOLINT + enum WiFiComponentState { /** Nothing has been initialized yet. Internal AP, if configured, is disabled at this point. */ WIFI_COMPONENT_STATE_OFF = 0, @@ -253,7 +258,7 @@ class WiFiComponent : public Component { #endif network::IPAddress get_dns_address(int num); - network::IPAddress get_ip_address(); + network::IPAddresses get_ip_addresses(); std::string get_use_address() const; void set_use_address(const std::string &use_address); @@ -288,7 +293,7 @@ class WiFiComponent : public Component { }); } - network::IPAddress wifi_sta_ip(); + network::IPAddresses wifi_sta_ip_addresses(); std::string wifi_ssid(); bssid_t wifi_bssid(); @@ -334,6 +339,9 @@ class WiFiComponent : public Component { bool is_captive_portal_active_(); bool is_esp32_improv_active_(); + void load_fast_connect_settings_(); + void save_fast_connect_settings_(); + #ifdef USE_ESP8266 static void wifi_event_callback(System_Event_t *event); void wifi_scan_done_callback_(void *arg, STATUS status); @@ -381,12 +389,17 @@ class WiFiComponent : public Component { optional output_power_; bool passive_scan_{false}; ESPPreferenceObject pref_; + ESPPreferenceObject fast_connect_pref_; bool has_saved_wifi_settings_{false}; #ifdef USE_WIFI_11KV_SUPPORT bool btm_{false}; bool rrm_{false}; #endif bool enable_on_boot_; + bool got_ipv4_address_{false}; +#if USE_NETWORK_IPV6 + uint8_t num_ipv6_addresses_{0}; +#endif /* USE_NETWORK_IPV6 */ Trigger<> *connect_trigger_{new Trigger<>()}; Trigger<> *disconnect_trigger_{new Trigger<>()}; diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 5d8aa7f749..44d77b4eed 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -131,7 +131,7 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { // TODO: is this needed? #if LWIP_IPV6 dns.type = IPADDR_TYPE_V4; -#endif +#endif /* LWIP_IPV6 */ if (manual_ip->dns1.is_set()) { dns = manual_ip->dns1; dns_setserver(0, &dns); @@ -144,12 +144,36 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { return true; } -network::IPAddress WiFiComponent::wifi_sta_ip() { +network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { if (!this->has_sta()) return {}; + network::IPAddresses addresses; tcpip_adapter_ip_info_t ip; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); - return network::IPAddress(&ip.ip); + esp_err_t err = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); + // TODO: do something smarter + // return false; + } else { + addresses[0] = network::IPAddress(&ip.ip); + } +#if USE_NETWORK_IPV6 + ip6_addr_t ipv6; + err = tcpip_adapter_get_ip6_global(TCPIP_ADAPTER_IF_STA, &ipv6); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_netif_get_ip6_gobal failed: %s", esp_err_to_name(err)); + } else { + addresses[1] = network::IPAddress(&ipv6); + } + err = tcpip_adapter_get_ip6_linklocal(TCPIP_ADAPTER_IF_STA, &ipv6); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_netif_get_ip6_linklocal failed: %s", esp_err_to_name(err)); + } else { + addresses[2] = network::IPAddress(&ipv6); + } +#endif /* USE_NETWORK_IPV6 */ + + return addresses; } bool WiFiComponent::wifi_apply_hostname_() { @@ -440,9 +464,9 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ buf[it.ssid_len] = '\0'; ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); -#if ENABLE_IPV6 +#if USE_NETWORK_IPV6 this->set_timeout(100, [] { WiFi.enableIpV6(); }); -#endif /* ENABLE_IPV6 */ +#endif /* USE_NETWORK_IPV6 */ break; } @@ -494,18 +518,26 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ auto it = info.got_ip.ip_info; ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip).c_str(), format_ip4_addr(it.gw).c_str()); + this->got_ipv4_address_ = true; +#if USE_NETWORK_IPV6 + s_sta_connecting = this->num_ipv6_addresses_ < USE_NETWORK_MIN_IPV6_ADDR_COUNT; +#else s_sta_connecting = false; +#endif /* USE_NETWORK_IPV6 */ break; } -#if ENABLE_IPV6 +#if USE_NETWORK_IPV6 case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: { auto it = info.got_ip6.ip6_info; ESP_LOGV(TAG, "Got IPv6 address=" IPV6STR, IPV62STR(it.ip)); + this->num_ipv6_addresses_++; + s_sta_connecting = !(this->got_ipv4_address_ & (this->num_ipv6_addresses_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT)); break; } -#endif /* ENABLE_IPV6 */ +#endif /* USE_NETWORK_IPV6 */ case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: { ESP_LOGV(TAG, "Event: Lost IP"); + this->got_ipv4_address_ = false; break; } case ESPHOME_EVENT_ID_WIFI_AP_START: { @@ -550,14 +582,14 @@ void WiFiComponent::wifi_pre_setup_() { } WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { auto status = WiFiClass::status(); - if (status == WL_CONNECTED) { - return WiFiSTAConnectStatus::CONNECTED; - } else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) { + if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) { return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; } else if (status == WL_NO_SSID_AVAIL) { return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; } else if (s_sta_connecting) { return WiFiSTAConnectStatus::CONNECTING; + } else if (status == WL_CONNECTED) { + return WiFiSTAConnectStatus::CONNECTED; } return WiFiSTAConnectStatus::IDLE; } @@ -675,7 +707,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.ssid)); + strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.password)); } conf.ap.pairwise_cipher = WIFI_CIPHER_TYPE_CCMP; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 15b0c65641..838250972b 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -17,16 +17,18 @@ extern "C" { #include "lwip/dhcp.h" #include "lwip/init.h" // LWIP_VERSION_ #include "lwip/apps/sntp.h" -#if LWIP_IPV6 #include "lwip/netif.h" // struct netif #include -#endif #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) #include "LwipDhcpServer.h" +#if USE_ARDUINO_VERSION_CODE < VERSION_CODE(3, 1, 0) +#include +#include "ESP8266WiFiAP.h" #define wifi_softap_set_dhcps_lease(lease) dhcpSoftAP.set_dhcps_lease(lease) #define wifi_softap_set_dhcps_lease_time(time) dhcpSoftAP.set_dhcps_lease_time(time) #define wifi_softap_set_dhcps_offer_option(offer, mode) dhcpSoftAP.set_dhcps_offer_option(offer, mode) #endif +#endif } #include "esphome/core/helpers.h" @@ -185,12 +187,15 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { return ret; } -network::IPAddress WiFiComponent::wifi_sta_ip() { +network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { if (!this->has_sta()) return {}; - struct ip_info ip {}; - wifi_get_ip_info(STATION_IF, &ip); - return network::IPAddress(&ip.ip); + network::IPAddresses addresses; + uint8_t index = 0; + for (auto &addr : addrList) { + addresses[index++] = addr.ipFromNetifNum(); + } + return addresses; } bool WiFiComponent::wifi_apply_hostname_() { const std::string &hostname = App.get_name(); @@ -327,17 +332,20 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { return false; } -#if ENABLE_IPV6 - for (bool configured = false; !configured;) { +#if USE_NETWORK_IPV6 + bool connected = false; + while (!connected) { + uint8_t ipv6_addr_count = 0; for (auto addr : addrList) { ESP_LOGV(TAG, "Address %s", addr.toString().c_str()); - if ((configured = !addr.isLocal() && addr.isV6())) { - break; + if (addr.isV6()) { + ipv6_addr_count++; } } delay(500); // NOLINT + connected = (ipv6_addr_count >= USE_NETWORK_MIN_IPV6_ADDR_COUNT); } -#endif +#endif /* USE_NETWORK_IPV6 */ if (ap.get_channel().has_value()) { ret = wifi_set_channel(*ap.get_channel()); @@ -717,7 +725,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { return false; } -#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) && USE_ARDUINO_VERSION_CODE < VERSION_CODE(3, 1, 0) dhcpSoftAP.begin(&info); #endif @@ -741,12 +749,16 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { return false; } +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0) + ESP8266WiFiClass::softAPDhcpServer().setRouter(true); // send ROUTER option with netif's gateway IP +#else uint8_t mode = 1; // bit0, 1 enables router information from ESP8266 SoftAP DHCP server. if (!wifi_softap_set_dhcps_offer_option(OFFER_ROUTER, &mode)) { ESP_LOGV(TAG, "wifi_softap_set_dhcps_offer_option failed!"); return false; } +#endif if (!wifi_softap_dhcps_start()) { ESP_LOGV(TAG, "Starting SoftAP DHCPS failed!"); diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 0035733553..ebb2fb92ea 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -39,14 +39,11 @@ static const char *const TAG = "wifi_esp32"; static EventGroupHandle_t s_wifi_event_group; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static QueueHandle_t s_event_queue; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static esp_netif_t *s_sta_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - #ifdef USE_WIFI_AP -static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -#endif // USE_WIFI_AP - +static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +#endif // USE_WIFI_AP static bool s_sta_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static bool s_sta_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static bool s_sta_got_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static bool s_ap_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static bool s_sta_connect_not_found = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static bool s_sta_connect_error = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -66,9 +63,9 @@ struct IDFWiFiEvent { wifi_event_ap_probe_req_rx_t ap_probe_req_rx; wifi_event_bss_rssi_low_t bss_rssi_low; ip_event_got_ip_t ip_got_ip; -#if ENABLE_IPV6 +#if USE_NETWORK_IPV6 ip_event_got_ip6_t ip_got_ip6; -#endif /* ENABLE_IPV6 */ +#endif /* USE_NETWORK_IPV6 */ ip_event_ap_staipassigned_t ip_ap_staipassigned; } data; }; @@ -92,7 +89,7 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi memcpy(&event.data.sta_disconnected, event_data, sizeof(wifi_event_sta_disconnected_t)); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { memcpy(&event.data.ip_got_ip, event_data, sizeof(ip_event_got_ip_t)); -#if ENABLE_IPV6 +#if USE_NETWORK_IPV6 } else if (event_base == IP_EVENT && event_id == IP_EVENT_GOT_IP6) { memcpy(&event.data.ip_got_ip6, event_data, sizeof(ip_event_got_ip6_t)); #endif @@ -411,7 +408,6 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // may be called from wifi_station_connect s_sta_connecting = true; s_sta_connected = false; - s_sta_got_ip = false; s_sta_connect_error = false; s_sta_connect_not_found = false; @@ -476,17 +472,29 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { return true; } -network::IPAddress WiFiComponent::wifi_sta_ip() { +network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { if (!this->has_sta()) return {}; + network::IPAddresses addresses; esp_netif_ip_info_t ip; esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip); if (err != ESP_OK) { ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); // TODO: do something smarter // return false; + } else { + addresses[0] = network::IPAddress(&ip.ip); } - return network::IPAddress(&ip.ip); +#if USE_NETWORK_IPV6 + struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; + uint8_t count = 0; + count = esp_netif_get_all_ip6(s_sta_netif, if_ip6s); + assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES); + for (int i = 0; i < count; i++) { + addresses[i + 1] = network::IPAddress(&if_ip6s[i]); + } +#endif /* USE_NETWORK_IPV6 */ + return addresses; } bool WiFiComponent::wifi_apply_hostname_() { @@ -521,7 +529,7 @@ const char *get_auth_mode_str(uint8_t mode) { std::string format_ip4_addr(const esp_ip4_addr_t &ip) { return str_snprintf(IPSTR, 15, IP2STR(&ip)); } #if LWIP_IPV6 std::string format_ip6_addr(const esp_ip6_addr_t &ip) { return str_snprintf(IPV6STR, 39, IPV62STR(ip)); } -#endif +#endif /* LWIP_IPV6 */ const char *get_disconnect_reason_str(uint8_t reason) { switch (reason) { case WIFI_REASON_AUTH_EXPIRE: @@ -658,22 +666,23 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) { const auto &it = data->data.ip_got_ip; -#if ENABLE_IPV6 +#if USE_NETWORK_IPV6 esp_netif_create_ip6_linklocal(s_sta_netif); -#endif /* ENABLE_IPV6 */ +#endif /* USE_NETWORK_IPV6 */ ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip_info.ip).c_str(), format_ip4_addr(it.ip_info.gw).c_str()); - s_sta_got_ip = true; + this->got_ipv4_address_ = true; -#if ENABLE_IPV6 +#if USE_NETWORK_IPV6 } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_GOT_IP6) { const auto &it = data->data.ip_got_ip6; ESP_LOGV(TAG, "Event: Got IPv6 address=%s", format_ip6_addr(it.ip6_info.ip).c_str()); -#endif /* ENABLE_IPV6 */ + this->num_ipv6_addresses_++; +#endif /* USE_NETWORK_IPV6 */ } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_LOST_IP) { ESP_LOGV(TAG, "Event: Lost IP"); - s_sta_got_ip = false; + this->got_ipv4_address_ = false; } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_SCAN_DONE) { const auto &it = data->data.sta_scan_done; @@ -737,8 +746,14 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { - if (s_sta_connected && s_sta_got_ip) { + if (s_sta_connected && this->got_ipv4_address_) { +#if USE_NETWORK_IPV6 + if (this->num_ipv6_addresses_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT) { + return WiFiSTAConnectStatus::CONNECTED; + } +#else return WiFiSTAConnectStatus::CONNECTED; +#endif /* USE_NETWORK_IPV6 */ } if (s_sta_connect_error) { return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 04e21ba1a1..50707a27e3 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -81,7 +81,7 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { return true; } -network::IPAddress WiFiComponent::wifi_sta_ip() { +network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { if (!this->has_sta()) return {}; return {WiFi.localIP()}; diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index c71203a877..2bb1af5489 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -6,6 +6,7 @@ #include "lwip/dns.h" #include "lwip/err.h" #include "lwip/netif.h" +#include #include "esphome/core/application.h" #include "esphome/core/hal.h" @@ -173,7 +174,14 @@ std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } -network::IPAddress WiFiComponent::wifi_sta_ip() { return {(const ip_addr_t *) WiFi.localIP()}; } +network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { + network::IPAddresses addresses; + uint8_t index = 0; + for (auto addr : addrList) { + addresses[index++] = addr.ipFromNetifNum(); + } + return addresses; +} network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { diff --git a/esphome/components/wifi/wpa2_eap.py b/esphome/components/wifi/wpa2_eap.py index 3cb60e6175..3985dfef18 100644 --- a/esphome/components/wifi/wpa2_eap.py +++ b/esphome/components/wifi/wpa2_eap.py @@ -3,6 +3,7 @@ The cryptography package is loaded lazily in the functions so that it doesn't crash if it's not installed. """ + import logging from pathlib import Path diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 659fd08db1..75513712dd 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -37,7 +37,16 @@ CONFIG_SCHEMA = cv.Schema( { cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( IPAddressWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ).extend(cv.polling_component_schema("1s")), + ) + .extend(cv.polling_component_schema("1s")) + .extend( + { + cv.Optional(f"address_{x}"): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ) + for x in range(5) + } + ), cv.Optional(CONF_SCAN_RESULTS): text_sensor.text_sensor_schema( ScanResultsWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ).extend(cv.polling_component_schema("60s")), @@ -65,9 +74,15 @@ async def setup_conf(config, key): async def to_code(config): - await setup_conf(config, CONF_IP_ADDRESS) await setup_conf(config, CONF_SSID) await setup_conf(config, CONF_BSSID) await setup_conf(config, CONF_MAC_ADDRESS) await setup_conf(config, CONF_SCAN_RESULTS) await setup_conf(config, CONF_DNS_ADDRESS) + if conf := config.get(CONF_IP_ADDRESS): + wifi_info = await text_sensor.new_text_sensor(config[CONF_IP_ADDRESS]) + await cg.register_component(wifi_info, config[CONF_IP_ADDRESS]) + for x in range(5): + if sensor_conf := conf.get(f"address_{x}"): + sens = await text_sensor.new_text_sensor(sensor_conf) + cg.add(wifi_info.add_ip_sensors(x, sens)) diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index 35ce108c86..6f189da3a3 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/wifi/wifi_component.h" +#include namespace esphome { namespace wifi_info { @@ -10,18 +11,29 @@ namespace wifi_info { class IPAddressWiFiInfo : public PollingComponent, public text_sensor::TextSensor { public: void update() override { - auto ip = wifi::global_wifi_component->wifi_sta_ip(); - if (ip != this->last_ip_) { - this->last_ip_ = ip; - this->publish_state(ip.str()); + auto ips = wifi::global_wifi_component->wifi_sta_ip_addresses(); + if (ips != this->last_ips_) { + this->last_ips_ = ips; + this->publish_state(ips[0].str()); + uint8_t sensor = 0; + for (auto &ip : ips) { + if (ip.is_set()) { + if (this->ip_sensors_[sensor] != nullptr) { + this->ip_sensors_[sensor]->publish_state(ip.str()); + } + sensor++; + } + } } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } std::string unique_id() override { return get_mac_address() + "-wifiinfo-ip"; } void dump_config() override; + void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; } protected: - network::IPAddress last_ip_; + network::IPAddresses last_ips_; + std::array ip_sensors_; }; class DNSAddressWifiInfo : public PollingComponent, public text_sensor::TextSensor { diff --git a/esphome/components/wireguard/__init__.py b/esphome/components/wireguard/__init__.py index acb5f690ec..2d68cd001e 100644 --- a/esphome/components/wireguard/__init__.py +++ b/esphome/components/wireguard/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( ) from esphome.components import time from esphome.core import TimePeriod +from esphome import automation CONF_NETMASK = "netmask" CONF_PRIVATE_KEY = "private_key" @@ -21,7 +22,7 @@ CONF_PEER_ALLOWED_IPS = "peer_allowed_ips" CONF_PEER_PERSISTENT_KEEPALIVE = "peer_persistent_keepalive" CONF_REQUIRE_CONNECTION_TO_PROCEED = "require_connection_to_proceed" -DEPENDENCIES = ["time", "esp32"] +DEPENDENCIES = ["time"] CODEOWNERS = ["@lhoracek", "@droscy", "@thomas0bernard"] # The key validation regex has been described by Jason Donenfeld himself @@ -30,6 +31,16 @@ _WG_KEY_REGEX = re.compile(r"^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw480]=$") wireguard_ns = cg.esphome_ns.namespace("wireguard") Wireguard = wireguard_ns.class_("Wireguard", cg.Component, cg.PollingComponent) +WireguardPeerOnlineCondition = wireguard_ns.class_( + "WireguardPeerOnlineCondition", automation.Condition +) +WireguardEnabledCondition = wireguard_ns.class_( + "WireguardEnabledCondition", automation.Condition +) +WireguardEnableAction = wireguard_ns.class_("WireguardEnableAction", automation.Action) +WireguardDisableAction = wireguard_ns.class_( + "WireguardDisableAction", automation.Action +) def _wireguard_key(value): @@ -109,6 +120,50 @@ async def to_code(config): # the '+1' modifier is relative to the device's own address that will # be automatically added to the provided list. cg.add_build_flag(f"-DCONFIG_WIREGUARD_MAX_SRC_IPS={len(allowed_ips) + 1}") - cg.add_library("droscy/esp_wireguard", "0.3.2") + cg.add_library("droscy/esp_wireguard", "0.4.0") await cg.register_component(var, config) + + +@automation.register_condition( + "wireguard.peer_online", + WireguardPeerOnlineCondition, + cv.Schema({cv.GenerateID(): cv.use_id(Wireguard)}), +) +async def wireguard_peer_up_to_code(config, condition_id, template_arg, args): + var = cg.new_Pvariable(condition_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +@automation.register_condition( + "wireguard.enabled", + WireguardEnabledCondition, + cv.Schema({cv.GenerateID(): cv.use_id(Wireguard)}), +) +async def wireguard_enabled_to_code(config, condition_id, template_arg, args): + var = cg.new_Pvariable(condition_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +@automation.register_action( + "wireguard.enable", + WireguardEnableAction, + cv.Schema({cv.GenerateID(): cv.use_id(Wireguard)}), +) +async def wireguard_enable_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +@automation.register_action( + "wireguard.disable", + WireguardDisableAction, + cv.Schema({cv.GenerateID(): cv.use_id(Wireguard)}), +) +async def wireguard_disable_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var diff --git a/esphome/components/wireguard/binary_sensor.py b/esphome/components/wireguard/binary_sensor.py index 14ff2b0159..bf60aaa1d6 100644 --- a/esphome/components/wireguard/binary_sensor.py +++ b/esphome/components/wireguard/binary_sensor.py @@ -4,11 +4,13 @@ from esphome.components import binary_sensor from esphome.const import ( CONF_STATUS, DEVICE_CLASS_CONNECTIVITY, + ENTITY_CATEGORY_DIAGNOSTIC, ) from . import Wireguard CONF_WIREGUARD_ID = "wireguard_id" +CONF_ENABLED = "enabled" DEPENDENCIES = ["wireguard"] @@ -17,6 +19,9 @@ CONFIG_SCHEMA = { cv.Optional(CONF_STATUS): binary_sensor.binary_sensor_schema( device_class=DEVICE_CLASS_CONNECTIVITY, ), + cv.Optional(CONF_ENABLED): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), } @@ -26,3 +31,7 @@ async def to_code(config): if status_config := config.get(CONF_STATUS): sens = await binary_sensor.new_binary_sensor(status_config) cg.add(parent.set_status_sensor(sens)) + + if enabled_config := config.get(CONF_ENABLED): + sens = await binary_sensor.new_binary_sensor(enabled_config) + cg.add(parent.set_enabled_sensor(sens)) diff --git a/esphome/components/wireguard/wireguard.cpp b/esphome/components/wireguard/wireguard.cpp index f89a5ebbad..17ebc701e3 100644 --- a/esphome/components/wireguard/wireguard.cpp +++ b/esphome/components/wireguard/wireguard.cpp @@ -1,7 +1,5 @@ #include "wireguard.h" -#ifdef USE_ESP32 - #include #include #include @@ -11,26 +9,20 @@ #include "esphome/core/time.h" #include "esphome/components/network/util.h" -#include - #include - -// includes for resume/suspend wdt -#if defined(USE_ESP_IDF) -#include -#if ESP_IDF_VERSION_MAJOR >= 5 -#include -#endif -#elif defined(USE_ARDUINO) -#include -#endif +#include namespace esphome { namespace wireguard { static const char *const TAG = "wireguard"; -static const char *const LOGMSG_PEER_STATUS = "WireGuard remote peer is %s (latest handshake %s)"; +/* + * Cannot use `static const char*` for LOGMSG_PEER_STATUS on esp8266 platform + * because log messages in `Wireguard::update()` method fail. + */ +#define LOGMSG_PEER_STATUS "WireGuard remote peer is %s (latest handshake %s)" + static const char *const LOGMSG_ONLINE = "online"; static const char *const LOGMSG_OFFLINE = "offline"; @@ -48,6 +40,8 @@ void Wireguard::setup() { if (this->preshared_key_.length() > 0) this->wg_config_.preshared_key = this->preshared_key_.c_str(); + this->publish_enabled_state(); + this->wg_initialized_ = esp_wireguard_init(&(this->wg_config_), &(this->wg_ctx_)); if (this->wg_initialized_ == ESP_OK) { @@ -68,6 +62,10 @@ void Wireguard::setup() { } void Wireguard::loop() { + if (!this->enabled_) { + return; + } + if ((this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) && (!network::is_connected())) { ESP_LOGV(TAG, "local network connection has been lost, stopping WireGuard..."); this->stop_connection_(); @@ -79,8 +77,9 @@ void Wireguard::update() { time_t lhs = this->get_latest_handshake(); bool lhs_updated = (lhs > this->latest_saved_handshake_); - ESP_LOGV(TAG, "handshake: latest=%.0f, saved=%.0f, updated=%d", (double) lhs, (double) this->latest_saved_handshake_, - (int) lhs_updated); + ESP_LOGV(TAG, "enabled=%d, connected=%d, peer_up=%d, handshake: current=%.0f latest=%.0f updated=%d", + (int) this->enabled_, (int) (this->wg_connected_ == ESP_OK), (int) peer_up, (double) lhs, + (double) this->latest_saved_handshake_, (int) lhs_updated); if (lhs_updated) { this->latest_saved_handshake_ = lhs; @@ -102,13 +101,13 @@ void Wireguard::update() { if (this->wg_peer_offline_time_ == 0) { ESP_LOGW(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str()); this->wg_peer_offline_time_ = millis(); - } else { + } else if (this->enabled_) { ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str()); this->start_connection_(); } // check reboot timeout every time the peer is down - if (this->reboot_timeout_ > 0) { + if (this->enabled_ && this->reboot_timeout_ > 0) { if (millis() - this->wg_peer_offline_time_ > this->reboot_timeout_) { ESP_LOGE(TAG, "WireGuard remote peer is unreachable, rebooting..."); App.reboot(); @@ -154,7 +153,7 @@ void Wireguard::dump_config() { void Wireguard::on_shutdown() { this->stop_connection_(); } -bool Wireguard::can_proceed() { return (this->proceed_allowed_ || this->is_peer_up()); } +bool Wireguard::can_proceed() { return (this->proceed_allowed_ || this->is_peer_up() || !this->enabled_); } bool Wireguard::is_peer_up() const { return (this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) && @@ -187,6 +186,7 @@ void Wireguard::set_srctime(time::RealTimeClock *srctime) { this->srctime_ = src #ifdef USE_BINARY_SENSOR void Wireguard::set_status_sensor(binary_sensor::BinarySensor *sensor) { this->status_sensor_ = sensor; } +void Wireguard::set_enabled_sensor(binary_sensor::BinarySensor *sensor) { this->enabled_sensor_ = sensor; } #endif #ifdef USE_SENSOR @@ -199,7 +199,35 @@ void Wireguard::set_address_sensor(text_sensor::TextSensor *sensor) { this->addr void Wireguard::disable_auto_proceed() { this->proceed_allowed_ = false; } +void Wireguard::enable() { + this->enabled_ = true; + ESP_LOGI(TAG, "WireGuard enabled"); + this->publish_enabled_state(); +} + +void Wireguard::disable() { + this->enabled_ = false; + this->defer(std::bind(&Wireguard::stop_connection_, this)); // defer to avoid blocking running loop + ESP_LOGI(TAG, "WireGuard disabled"); + this->publish_enabled_state(); +} + +void Wireguard::publish_enabled_state() { +#ifdef USE_BINARY_SENSOR + if (this->enabled_sensor_ != nullptr) { + this->enabled_sensor_->publish_state(this->enabled_); + } +#endif +} + +bool Wireguard::is_enabled() { return this->enabled_; } + void Wireguard::start_connection_() { + if (!this->enabled_) { + ESP_LOGV(TAG, "WireGuard is disabled, cannot start connection"); + return; + } + if (this->wg_initialized_ != ESP_OK) { ESP_LOGE(TAG, "cannot start WireGuard, initialization in error with code %d", this->wg_initialized_); return; @@ -221,20 +249,13 @@ void Wireguard::start_connection_() { } ESP_LOGD(TAG, "starting WireGuard connection..."); - - /* - * The function esp_wireguard_connect() contains a DNS resolution - * that could trigger the watchdog, so before it we suspend (or - * increase the time, it depends on the platform) the wdt and - * then we resume the normal timeout. - */ - suspend_wdt(); - ESP_LOGV(TAG, "executing esp_wireguard_connect"); this->wg_connected_ = esp_wireguard_connect(&(this->wg_ctx_)); - resume_wdt(); if (this->wg_connected_ == ESP_OK) { ESP_LOGI(TAG, "WireGuard connection started"); + } else if (this->wg_connected_ == ESP_ERR_RETRY) { + ESP_LOGD(TAG, "WireGuard is waiting for endpoint IP address to be available"); + return; } else { ESP_LOGW(TAG, "cannot start WireGuard connection, error code %d", this->wg_connected_); return; @@ -264,44 +285,7 @@ void Wireguard::stop_connection_() { } } -void suspend_wdt() { -#if defined(USE_ESP_IDF) -#if ESP_IDF_VERSION_MAJOR >= 5 - ESP_LOGV(TAG, "temporarily increasing wdt timeout to 15000 ms"); - esp_task_wdt_config_t wdtc; - wdtc.timeout_ms = 15000; - wdtc.idle_core_mask = 0; - wdtc.trigger_panic = false; - esp_task_wdt_reconfigure(&wdtc); -#else - ESP_LOGV(TAG, "temporarily increasing wdt timeout to 15 seconds"); - esp_task_wdt_init(15, false); -#endif -#elif defined(USE_ARDUINO) - ESP_LOGV(TAG, "temporarily disabling the wdt"); - disableLoopWDT(); -#endif -} - -void resume_wdt() { -#if defined(USE_ESP_IDF) -#if ESP_IDF_VERSION_MAJOR >= 5 - wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000; - esp_task_wdt_reconfigure(&wdtc); - ESP_LOGV(TAG, "wdt resumed with %" PRIu32 " ms timeout", wdtc.timeout_ms); -#else - esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); - ESP_LOGV(TAG, "wdt resumed with %d seconds timeout", CONFIG_ESP_TASK_WDT_TIMEOUT_S); -#endif -#elif defined(USE_ARDUINO) - enableLoopWDT(); - ESP_LOGV(TAG, "wdt resumed"); -#endif -} - std::string mask_key(const std::string &key) { return (key.substr(0, 5) + "[...]="); } } // namespace wireguard } // namespace esphome - -#endif // USE_ESP32 diff --git a/esphome/components/wireguard/wireguard.h b/esphome/components/wireguard/wireguard.h index c47d9e6603..a0e9e27a1b 100644 --- a/esphome/components/wireguard/wireguard.h +++ b/esphome/components/wireguard/wireguard.h @@ -1,7 +1,5 @@ #pragma once -#ifdef USE_ESP32 - #include #include #include @@ -26,6 +24,7 @@ namespace esphome { namespace wireguard { +/// Main Wireguard component class. class Wireguard : public PollingComponent { public: void setup() override; @@ -53,6 +52,7 @@ class Wireguard : public PollingComponent { #ifdef USE_BINARY_SENSOR void set_status_sensor(binary_sensor::BinarySensor *sensor); + void set_enabled_sensor(binary_sensor::BinarySensor *sensor); #endif #ifdef USE_SENSOR @@ -66,6 +66,18 @@ class Wireguard : public PollingComponent { /// Block the setup step until peer is connected. void disable_auto_proceed(); + /// Enable the WireGuard component. + void enable(); + + /// Stop any running connection and disable the WireGuard component. + void disable(); + + /// Publish the enabled state if the enabled binary sensor is configured. + void publish_enabled_state(); + + /// Return if the WireGuard component is or is not enabled. + bool is_enabled(); + bool is_peer_up() const; time_t get_latest_handshake() const; @@ -87,6 +99,7 @@ class Wireguard : public PollingComponent { #ifdef USE_BINARY_SENSOR binary_sensor::BinarySensor *status_sensor_ = nullptr; + binary_sensor::BinarySensor *enabled_sensor_ = nullptr; #endif #ifdef USE_SENSOR @@ -100,6 +113,9 @@ class Wireguard : public PollingComponent { /// Set to false to block the setup step until peer is connected. bool proceed_allowed_ = true; + /// When false the wireguard link will not be established + bool enabled_ = true; + wireguard_config_t wg_config_ = ESP_WIREGUARD_CONFIG_DEFAULT(); wireguard_ctx_t wg_ctx_ = ESP_WIREGUARD_CONTEXT_DEFAULT(); @@ -128,7 +144,29 @@ void resume_wdt(); /// Strip most part of the key only for secure printing std::string mask_key(const std::string &key); +/// Condition to check if remote peer is online. +template class WireguardPeerOnlineCondition : public Condition, public Parented { + public: + bool check(Ts... x) override { return this->parent_->is_peer_up(); } +}; + +/// Condition to check if Wireguard component is enabled. +template class WireguardEnabledCondition : public Condition, public Parented { + public: + bool check(Ts... x) override { return this->parent_->is_enabled(); } +}; + +/// Action to enable Wireguard component. +template class WireguardEnableAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->enable(); } +}; + +/// Action to disable Wireguard component. +template class WireguardDisableAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->disable(); } +}; + } // namespace wireguard } // namespace esphome - -#endif // USE_ESP32 diff --git a/esphome/components/wled/__init__.py b/esphome/components/wled/__init__.py index 2795529203..396d5891d8 100644 --- a/esphome/components/wled/__init__.py +++ b/esphome/components/wled/__init__.py @@ -8,6 +8,8 @@ wled_ns = cg.esphome_ns.namespace("wled") WLEDLightEffect = wled_ns.class_("WLEDLightEffect", AddressableLightEffect) CONFIG_SCHEMA = cv.All(cv.Schema({}), cv.only_with_arduino) +CONF_SYNC_GROUP_MASK = "sync_group_mask" +CONF_BLANK_ON_START = "blank_on_start" @register_addressable_effect( @@ -16,10 +18,13 @@ CONFIG_SCHEMA = cv.All(cv.Schema({}), cv.only_with_arduino) "WLED", { cv.Optional(CONF_PORT, default=21324): cv.port, + cv.Optional(CONF_SYNC_GROUP_MASK, default=0): cv.int_range(min=0, max=255), + cv.Optional(CONF_BLANK_ON_START, default=True): cv.boolean, }, ) async def wled_light_effect_to_code(config, effect_id): effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) cg.add(effect.set_port(config[CONF_PORT])) - + cg.add(effect.set_sync_group_mask(config[CONF_SYNC_GROUP_MASK])) + cg.add(effect.set_blank_on_start(config[CONF_BLANK_ON_START])) return effect diff --git a/esphome/components/wled/wled_light_effect.cpp b/esphome/components/wled/wled_light_effect.cpp index 8c68bca6e3..84842dff39 100644 --- a/esphome/components/wled/wled_light_effect.cpp +++ b/esphome/components/wled/wled_light_effect.cpp @@ -13,6 +13,10 @@ #include #endif +#ifdef USE_BK72XX +#include +#endif + namespace esphome { namespace wled { @@ -29,7 +33,11 @@ WLEDLightEffect::WLEDLightEffect(const std::string &name) : AddressableLightEffe void WLEDLightEffect::start() { AddressableLightEffect::start(); - blank_at_ = 0; + if (this->blank_on_start_) { + this->blank_at_ = 0; + } else { + this->blank_at_ = UINT32_MAX; + } } void WLEDLightEffect::stop() { @@ -101,8 +109,11 @@ bool WLEDLightEffect::parse_frame_(light::AddressableLight &it, const uint8_t *p if (!parse_drgb_frame_(it, payload, size)) return false; } else { - if (!parse_notifier_frame_(it, payload, size)) + if (!parse_notifier_frame_(it, payload, size)) { return false; + } else { + timeout = UINT8_MAX; + } } break; @@ -143,8 +154,32 @@ bool WLEDLightEffect::parse_frame_(light::AddressableLight &it, const uint8_t *p } bool WLEDLightEffect::parse_notifier_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) { - // Packet needs to be empty - return size == 0; + // Receive at least RGBW and Brightness for all LEDs from WLED Sync Notification + // https://kno.wled.ge/interfaces/udp-notifier/ + // https://github.com/Aircoookie/WLED/blob/main/wled00/udp.cpp + + if (size < 34) { + return false; + } + + uint8_t payload_sync_group_mask = payload[34]; + + if (this->sync_group_mask_ && !(payload_sync_group_mask & this->sync_group_mask_)) { + ESP_LOGD(TAG, "sync group mask does not match"); + return false; + } + + uint8_t bri = payload[0]; + uint8_t r = esp_scale8(payload[1], bri); + uint8_t g = esp_scale8(payload[2], bri); + uint8_t b = esp_scale8(payload[3], bri); + uint8_t w = esp_scale8(payload[8], bri); + + for (auto &&led : it) { + led.set(Color(r, g, b, w)); + } + + return true; } bool WLEDLightEffect::parse_warls_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) { diff --git a/esphome/components/wled/wled_light_effect.h b/esphome/components/wled/wled_light_effect.h index 8f239276d7..a591e1fd1a 100644 --- a/esphome/components/wled/wled_light_effect.h +++ b/esphome/components/wled/wled_light_effect.h @@ -21,6 +21,8 @@ class WLEDLightEffect : public light::AddressableLightEffect { void stop() override; void apply(light::AddressableLight &it, const Color ¤t_color) override; void set_port(uint16_t port) { this->port_ = port; } + void set_sync_group_mask(uint8_t mask) { this->sync_group_mask_ = mask; } + void set_blank_on_start(bool blank) { this->blank_on_start_ = blank; } protected: void blank_all_leds_(light::AddressableLight &it); @@ -35,6 +37,8 @@ class WLEDLightEffect : public light::AddressableLightEffect { std::unique_ptr udp_; uint32_t blank_at_{0}; uint32_t dropped_{0}; + uint8_t sync_group_mask_{0}; + bool blank_on_start_{true}; }; } // namespace wled diff --git a/esphome/components/x9c/x9c.cpp b/esphome/components/x9c/x9c.cpp index ff7777e71f..32a1375f02 100644 --- a/esphome/components/x9c/x9c.cpp +++ b/esphome/components/x9c/x9c.cpp @@ -7,6 +7,10 @@ namespace x9c { static const char *const TAG = "x9c.output"; void X9cOutput::trim_value(int change_amount) { + if (change_amount == 0) { + return; + } + if (change_amount > 0) { // Set change direction this->ud_pin_->digital_write(true); } else { @@ -45,17 +49,17 @@ void X9cOutput::setup() { if (this->initial_value_ <= 0.50) { this->trim_value(-101); // Set min value (beyond 0) - this->trim_value((int) (this->initial_value_ * 100)); + this->trim_value(static_cast(roundf(this->initial_value_ * 100))); } else { this->trim_value(101); // Set max value (beyond 100) - this->trim_value((int) (this->initial_value_ * 100) - 100); + this->trim_value(static_cast(roundf(this->initial_value_ * 100) - 100)); } this->pot_value_ = this->initial_value_; this->write_state(this->initial_value_); } void X9cOutput::write_state(float state) { - this->trim_value((int) ((state - this->pot_value_) * 100)); + this->trim_value(static_cast(roundf((state - this->pot_value_) * 100))); this->pot_value_ = state; } diff --git a/esphome/components/xl9535/xl9535.cpp b/esphome/components/xl9535/xl9535.cpp index 8f4f556b4a..93c65a4cb7 100644 --- a/esphome/components/xl9535/xl9535.cpp +++ b/esphome/components/xl9535/xl9535.cpp @@ -36,14 +36,14 @@ bool XL9535Component::digital_read(uint8_t pin) { return state; } - state = (port & (pin - 10)) != 0; + state = (port & (1 << (pin - 10))) != 0; } else { if (this->read_register(XL9535_INPUT_PORT_0_REGISTER, &port, 1) != i2c::ERROR_OK) { this->status_set_warning(); return state; } - state = (port & pin) != 0; + state = (port & (1 << pin)) != 0; } this->status_clear_warning(); diff --git a/esphome/components/xpt2046/touchscreen/__init__.py b/esphome/components/xpt2046/touchscreen/__init__.py index 9f08f38c3f..d45f309a3b 100644 --- a/esphome/components/xpt2046/touchscreen/__init__.py +++ b/esphome/components/xpt2046/touchscreen/__init__.py @@ -15,35 +15,11 @@ XPT2046Component = XPT2046_ns.class_( spi.SPIDevice, ) - CONF_CALIBRATION_X_MIN = "calibration_x_min" CONF_CALIBRATION_X_MAX = "calibration_x_max" CONF_CALIBRATION_Y_MIN = "calibration_y_min" CONF_CALIBRATION_Y_MAX = "calibration_y_max" - -def validate_xpt2046(config): - if ( - abs( - cv.int_(config[CONF_CALIBRATION_X_MAX]) - - cv.int_(config[CONF_CALIBRATION_X_MIN]) - ) - < 1000 - ): - raise cv.Invalid("Calibration X values difference < 1000") - - if ( - abs( - cv.int_(config[CONF_CALIBRATION_Y_MAX]) - - cv.int_(config[CONF_CALIBRATION_Y_MIN]) - ) - < 1000 - ): - raise cv.Invalid("Calibration Y values difference < 1000") - - return config - - CONFIG_SCHEMA = cv.All( touchscreen.TOUCHSCREEN_SCHEMA.extend( cv.Schema( @@ -52,42 +28,41 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_INTERRUPT_PIN): cv.All( pins.internal_gpio_input_pin_schema ), - cv.Optional(CONF_CALIBRATION_X_MIN, default=0): cv.int_range( - min=0, max=4095 - ), - cv.Optional(CONF_CALIBRATION_X_MAX, default=4095): cv.int_range( - min=0, max=4095 - ), - cv.Optional(CONF_CALIBRATION_Y_MIN, default=0): cv.int_range( - min=0, max=4095 - ), - cv.Optional(CONF_CALIBRATION_Y_MAX, default=4095): cv.int_range( - min=0, max=4095 - ), cv.Optional(CONF_THRESHOLD, default=400): cv.int_range(min=0, max=4095), + cv.Optional( + touchscreen.CONF_CALIBRATION + ): touchscreen.calibration_schema(4095), + cv.Optional(CONF_CALIBRATION_X_MIN): cv.invalid( + "Deprecated: use the new 'calibration' configuration variable" + ), + cv.Optional(CONF_CALIBRATION_X_MAX): cv.invalid( + "Deprecated: use the new 'calibration' configuration variable" + ), + cv.Optional(CONF_CALIBRATION_Y_MIN): cv.invalid( + "Deprecated: use the new 'calibration' configuration variable" + ), + cv.Optional(CONF_CALIBRATION_Y_MAX): cv.invalid( + "Deprecated: use the new 'calibration' configuration variable" + ), + cv.Optional(CONF_CALIBRATION_Y_MAX): cv.invalid( + "Deprecated: use the new 'calibration' configuration variable" + ), + cv.Optional("report_interval"): cv.invalid( + "Deprecated: use the 'update_interval' configuration variable" + ), }, ) ).extend(spi.spi_device_schema()), - validate_xpt2046, ) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await touchscreen.register_touchscreen(var, config) await spi.register_spi_device(var, config) + await touchscreen.register_touchscreen(var, config) cg.add(var.set_threshold(config[CONF_THRESHOLD])) - cg.add( - var.set_calibration( - config[CONF_CALIBRATION_X_MIN], - config[CONF_CALIBRATION_X_MAX], - config[CONF_CALIBRATION_Y_MIN], - config[CONF_CALIBRATION_Y_MAX], - ) - ) - if CONF_INTERRUPT_PIN in config: pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) cg.add(var.set_irq_pin(pin)) diff --git a/esphome/components/xpt2046/touchscreen/xpt2046.cpp b/esphome/components/xpt2046/touchscreen/xpt2046.cpp index a268da06dd..a4e2b84656 100644 --- a/esphome/components/xpt2046/touchscreen/xpt2046.cpp +++ b/esphome/components/xpt2046/touchscreen/xpt2046.cpp @@ -32,9 +32,8 @@ void XPT2046Component::update_touches() { int16_t touch_pressure_1 = this->read_adc_(0xB1 /* touch_pressure_1 */); int16_t touch_pressure_2 = this->read_adc_(0xC1 /* touch_pressure_2 */); - ESP_LOGVV(TAG, "touch_pressure %d, %d", touch_pressure_1, touch_pressure_2); z_raw = touch_pressure_1 + 0Xfff - touch_pressure_2; - + ESP_LOGVV(TAG, "Touchscreen Update z = %d", z_raw); touch = (z_raw >= this->threshold_); if (touch) { read_adc_(0xD1 /* X */); // dummy Y measure, 1st is always noisy @@ -53,7 +52,7 @@ void XPT2046Component::update_touches() { x_raw = best_two_avg(data[1], data[3], data[5]); y_raw = best_two_avg(data[0], data[2], data[4]); - ESP_LOGV(TAG, "Touchscreen Update [%d, %d], z = %d", x_raw, y_raw, z_raw); + ESP_LOGD(TAG, "Touchscreen Update [%d, %d], z = %d", x_raw, y_raw, z_raw); this->add_raw_touch_position_(0, x_raw, y_raw, z_raw); } @@ -77,7 +76,7 @@ void XPT2046Component::dump_config() { LOG_UPDATE_INTERVAL(this); } -float XPT2046Component::get_setup_priority() const { return setup_priority::DATA; } +// float XPT2046Component::get_setup_priority() const { return setup_priority::DATA; } int16_t XPT2046Component::best_two_avg(int16_t value1, int16_t value2, int16_t value3) { int16_t delta_a, delta_b, delta_c; diff --git a/esphome/components/xpt2046/touchscreen/xpt2046.h b/esphome/components/xpt2046/touchscreen/xpt2046.h index ff866bc86b..a635c08f82 100644 --- a/esphome/components/xpt2046/touchscreen/xpt2046.h +++ b/esphome/components/xpt2046/touchscreen/xpt2046.h @@ -23,7 +23,7 @@ class XPT2046Component : public Touchscreen, void setup() override; void dump_config() override; - float get_setup_priority() const override; + // float get_setup_priority() const override; protected: static int16_t best_two_avg(int16_t value1, int16_t value2, int16_t value3); diff --git a/esphome/config.py b/esphome/config.py index 745883c2ef..c5764dd4f2 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -1,10 +1,11 @@ +from __future__ import annotations import abc import functools import heapq import logging import re -from typing import Optional, Union +from typing import Union, Any from contextlib import contextmanager import contextvars @@ -39,6 +40,17 @@ _LOGGER = logging.getLogger(__name__) def iter_components(config): + for domain, conf in config.items(): + component = get_component(domain) + yield domain, component + if component.is_platform_component: + for p_config in conf: + p_name = f"{domain}.{p_config[CONF_PLATFORM]}" + platform = get_platform(domain, p_config[CONF_PLATFORM]) + yield p_name, platform + + +def iter_component_configs(config): for domain, conf in config.items(): component = get_component(domain) if component.multi_conf: @@ -65,7 +77,7 @@ def _path_begins_with(path: ConfigPath, other: ConfigPath) -> bool: @functools.total_ordering class _ValidationStepTask: - def __init__(self, priority: float, id_number: int, step: "ConfigValidationStep"): + def __init__(self, priority: float, id_number: int, step: ConfigValidationStep): self.priority = priority self.id_number = id_number self.step = step @@ -119,7 +131,7 @@ class Config(OrderedDict, fv.FinalValidateConfig): ) self.errors.append(error) - def add_validation_step(self, step: "ConfigValidationStep"): + def add_validation_step(self, step: ConfigValidationStep): id_num = self._validation_tasks_id self._validation_tasks_id += 1 heapq.heappush( @@ -161,7 +173,7 @@ class Config(OrderedDict, fv.FinalValidateConfig): conf = conf[key] conf[path[-1]] = value - def get_error_for_path(self, path: ConfigPath) -> Optional[vol.Invalid]: + def get_error_for_path(self, path: ConfigPath) -> vol.Invalid | None: for err in self.errors: if self.get_deepest_path(err.path) == path: self.errors.remove(err) @@ -170,7 +182,7 @@ class Config(OrderedDict, fv.FinalValidateConfig): def get_deepest_document_range_for_path( self, path: ConfigPath, get_key: bool = False - ) -> Optional[ESPHomeDataBase]: + ) -> ESPHomeDataBase | None: data = self doc_range = None for index, path_item in enumerate(path): @@ -281,8 +293,7 @@ class ConfigValidationStep(abc.ABC): priority: float = 0.0 @abc.abstractmethod - def run(self, result: Config) -> None: - ... + def run(self, result: Config) -> None: ... # noqa: E704 class LoadValidationStep(ConfigValidationStep): @@ -303,8 +314,14 @@ class LoadValidationStep(ConfigValidationStep): # Ignore top-level keys starting with a dot return result.add_output_path([self.domain], self.domain) - result[self.domain] = self.conf component = get_component(self.domain) + if ( + component is not None + and component.multi_conf_no_default + and isinstance(self.conf, core.AutoLoad) + ): + self.conf = [] + result[self.domain] = self.conf path = [self.domain] if component is None: result.add_str_error(f"Component not found: {self.domain}", path) @@ -424,7 +441,10 @@ class MetadataValidationStep(ConfigValidationStep): def run(self, result: Config) -> None: if self.conf is None: - result[self.domain] = self.conf = {} + if self.comp.multi_conf and self.comp.multi_conf_no_default: + result[self.domain] = self.conf = [] + else: + result[self.domain] = self.conf = {} success = True for dependency in self.comp.dependencies: @@ -714,7 +734,9 @@ class PinUseValidationCheck(ConfigValidationStep): pins.PIN_SCHEMA_REGISTRY.final_validate(result) -def validate_config(config, command_line_substitutions) -> Config: +def validate_config( + config: dict[str, Any], command_line_substitutions: dict[str, Any] +) -> Config: result = Config() loader.clear_component_meta_finders() @@ -878,24 +900,23 @@ class InvalidYAMLError(EsphomeError): self.base_exc = base_exc -def _load_config(command_line_substitutions): +def _load_config(command_line_substitutions: dict[str, Any]) -> Config: + """Load the configuration file.""" try: config = yaml_util.load_yaml(CORE.config_path) except EsphomeError as e: raise InvalidYAMLError(e) from e try: - result = validate_config(config, command_line_substitutions) + return validate_config(config, command_line_substitutions) except EsphomeError: raise except Exception: _LOGGER.error("Unexpected exception while reading configuration:") raise - return result - -def load_config(command_line_substitutions): +def load_config(command_line_substitutions: dict[str, Any]) -> Config: try: return _load_config(command_line_substitutions) except vol.Invalid as err: diff --git a/esphome/config_helpers.py b/esphome/config_helpers.py index ac52c6ede2..7b47e097c8 100644 --- a/esphome/config_helpers.py +++ b/esphome/config_helpers.py @@ -1,9 +1,4 @@ -import json -import os - from esphome.const import CONF_ID -from esphome.core import CORE -from esphome.helpers import read_file class Extend: @@ -38,25 +33,6 @@ class Remove: return isinstance(b, Remove) and self.value == b.value -def read_config_file(path: str) -> str: - if CORE.vscode and ( - not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path) - ): - print( - json.dumps( - { - "type": "read_file", - "path": path, - } - ) - ) - data = json.loads(input()) - assert data["type"] == "file_response" - return data["content"] - - return read_file(path) - - def merge_config(full_old, full_new): def merge(old, new): if isinstance(new, dict): diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 8f2e080b46..358608cd35 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -29,9 +29,13 @@ from esphome.const import ( CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_RETAIN, + CONF_QOS, CONF_SETUP_PRIORITY, CONF_STATE_TOPIC, CONF_TOPIC, + CONF_YEAR, + CONF_MONTH, + CONF_DAY, CONF_HOUR, CONF_MINUTE, CONF_SECOND, @@ -57,6 +61,7 @@ from esphome.const import ( TYPE_GIT, TYPE_LOCAL, VALID_SUBSTITUTIONS_CHARACTERS, + __version__ as ESPHOME_VERSION, ) from esphome.core import ( CORE, @@ -816,21 +821,112 @@ positive_not_null_time_period = All( def time_of_day(value): - value = string(value) - try: - date = datetime.strptime(value, "%H:%M:%S") - except ValueError as err: - try: - date = datetime.strptime(value, "%H:%M:%S %p") - except ValueError: - # pylint: disable=raise-missing-from - raise Invalid(f"Invalid time of day: {err}") + return date_time(allowed_date=False, allowed_time=True)(value) - return { - CONF_HOUR: date.hour, - CONF_MINUTE: date.minute, - CONF_SECOND: date.second, - } + +def date_time(allowed_date: bool = True, allowed_time: bool = True): + + pattern_str = r"^" # Start of string + if allowed_date: + pattern_str += ( + r"(" # 1. Optional Date group + r"\d{4}-\d{1,2}-\d{1,2}" # Date + r"(?:\s(?=.+))?" # Space after date only if time is following + r")?" # End optional Date group + ) + if allowed_time: + pattern_str += ( + r"(" # 2. Optional Time group + r"(\d{1,2}:\d{2})" # 3. Hour/Minute + r"(:\d{2})?" # 4. Seconds + r"(" # 5. Optional AM/PM group + r"(\s)?" # 6. Optional Space + r"(?:AM|PM|am|pm)" # AM/PM string matching + r")?" # End optional AM/PM group + r")?" # End optional Time group + ) + pattern_str += r"$" # End of string + + pattern = re.compile(pattern_str) + + exc_message = "" + if allowed_date: + exc_message += "date" + if allowed_time: + exc_message += "/" + if allowed_time: + exc_message += "time" + + schema = Schema({}) + if allowed_date: + schema = schema.extend( + { + Optional(CONF_YEAR): int_range(min=1970, max=3000), + Optional(CONF_MONTH): int_range(min=1, max=12), + Optional(CONF_DAY): int_range(min=1, max=31), + } + ) + if allowed_time: + schema = schema.extend( + { + Optional(CONF_HOUR): int_range(min=0, max=23), + Optional(CONF_MINUTE): int_range(min=0, max=59), + Optional(CONF_SECOND): int_range(min=0, max=59), + } + ) + + def validator(value): + if isinstance(value, dict): + return schema(value) + value = string(value) + + match = pattern.match(value) + if match is None: + # pylint: disable=raise-missing-from + raise Invalid(f"Invalid {exc_message}: {value}") + + if allowed_date: + has_date = match[1] is not None + if allowed_time: + has_time = match[2] is not None + has_seconds = match[3] is not None + has_ampm = match[4] is not None + has_ampm_space = match[5] is not None + + format = "" + if allowed_date and has_date: + format += "%Y-%m-%d" + if allowed_time and has_time: + format += " " + if allowed_time and has_time: + format += "%H:%M" + if has_seconds: + format += ":%S" + if has_ampm_space: + format += " " + if has_ampm: + format += "%p" + + try: + date_obj = datetime.strptime(value, format) + except ValueError as err: + # pylint: disable=raise-missing-from + raise Invalid(f"Invalid {exc_message}: {err}") + + return_value = {} + if allowed_date and has_date: + return_value[CONF_YEAR] = date_obj.year + return_value[CONF_MONTH] = date_obj.month + return_value[CONF_DAY] = date_obj.day + + if allowed_time and has_time: + return_value[CONF_HOUR] = date_obj.hour + return_value[CONF_MINUTE] = date_obj.minute + return_value[CONF_SECOND] = date_obj.second if has_seconds else 0 + + return schema(return_value) + + return validator def mac_address(value): @@ -1778,6 +1874,7 @@ MQTT_COMPONENT_AVAILABILITY_SCHEMA = Schema( MQTT_COMPONENT_SCHEMA = Schema( { + Optional(CONF_QOS): All(requires_component("mqtt"), int_range(min=0, max=2)), Optional(CONF_RETAIN): All(requires_component("mqtt"), boolean), Optional(CONF_DISCOVERY): All(requires_component("mqtt"), boolean), Optional(CONF_STATE_TOPIC): All(requires_component("mqtt"), publish_topic), @@ -1895,6 +1992,16 @@ def version_number(value): raise Invalid("Not a valid version number") from e +def validate_esphome_version(value: str): + min_version = Version.parse(value) + current_version = Version.parse(ESPHOME_VERSION) + if current_version < min_version: + raise Invalid( + f"Your ESPHome version is too old. Please update to at least {min_version}" + ) + return value + + def platformio_version_constraint(value): # for documentation on valid version constraints: # https://docs.platformio.org/en/latest/core/userguide/platforms/cmd_install.html#cmd-platform-install @@ -2004,15 +2111,19 @@ def suppress_invalid(): pass -GIT_SCHEMA = { - Required(CONF_URL): url, - Optional(CONF_REF): git_ref, - Optional(CONF_USERNAME): string, - Optional(CONF_PASSWORD): string, -} -LOCAL_SCHEMA = { - Required(CONF_PATH): directory, -} +GIT_SCHEMA = Schema( + { + Required(CONF_URL): url, + Optional(CONF_REF): git_ref, + Optional(CONF_USERNAME): string, + Optional(CONF_PASSWORD): string, + } +) +LOCAL_SCHEMA = Schema( + { + Required(CONF_PATH): directory, + } +) def validate_source_shorthand(value): @@ -2053,8 +2164,8 @@ SOURCE_SCHEMA = Any( validate_source_shorthand, typed_schema( { - TYPE_GIT: Schema(GIT_SCHEMA), - TYPE_LOCAL: Schema(LOCAL_SCHEMA), + TYPE_GIT: GIT_SCHEMA, + TYPE_LOCAL: LOCAL_SCHEMA, } ), ) diff --git a/esphome/const.py b/esphome/const.py index 5de34b86cd..8e3fd59ff0 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.1.0-dev" +__version__ = "2024.4.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( @@ -112,6 +112,8 @@ CONF_CHANNELS = "channels" CONF_CHARACTERISTIC_UUID = "characteristic_uuid" CONF_CHIPSET = "chipset" CONF_CLEAR_IMPEDANCE = "clear_impedance" +CONF_CLIENT_CERTIFICATE = "client_certificate" +CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key" CONF_CLIENT_ID = "client_id" CONF_CLK_PIN = "clk_pin" CONF_CLOCK_PIN = "clock_pin" @@ -127,6 +129,7 @@ CONF_COLOR_BRIGHTNESS = "color_brightness" CONF_COLOR_CORRECT = "color_correct" CONF_COLOR_INTERLOCK = "color_interlock" CONF_COLOR_MODE = "color_mode" +CONF_COLOR_ORDER = "color_order" CONF_COLOR_PALETTE = "color_palette" CONF_COLOR_TEMPERATURE = "color_temperature" CONF_COLORS = "colors" @@ -173,6 +176,8 @@ CONF_DATA_PIN = "data_pin" CONF_DATA_PINS = "data_pins" CONF_DATA_RATE = "data_rate" CONF_DATA_TEMPLATE = "data_template" +CONF_DATE = "date" +CONF_DAY = "day" CONF_DAYS_OF_MONTH = "days_of_month" CONF_DAYS_OF_WEEK = "days_of_week" CONF_DC_PIN = "dc_pin" @@ -185,6 +190,7 @@ CONF_DEFAULT_MODE = "default_mode" CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high" CONF_DEFAULT_TARGET_TEMPERATURE_LOW = "default_target_temperature_low" CONF_DEFAULT_TRANSITION_LENGTH = "default_transition_length" +CONF_DEFAULTS = "defaults" CONF_DELAY = "delay" CONF_DELIMITER = "delimiter" CONF_DELTA = "delta" @@ -247,6 +253,7 @@ CONF_EXPORT_ACTIVE_ENERGY = "export_active_energy" CONF_EXPORT_REACTIVE_ENERGY = "export_reactive_energy" CONF_EXTERNAL_CLOCK_INPUT = "external_clock_input" CONF_EXTERNAL_COMPONENTS = "external_components" +CONF_EXTERNAL_TEMPERATURE = "external_temperature" CONF_EXTERNAL_VCC = "external_vcc" CONF_FALLING_EDGE = "falling_edge" CONF_FAMILY = "family" @@ -369,6 +376,7 @@ CONF_INTERRUPT_PIN = "interrupt_pin" CONF_INTERVAL = "interval" CONF_INVALID_COOLDOWN = "invalid_cooldown" CONF_INVERT = "invert" +CONF_INVERT_COLORS = "invert_colors" CONF_INVERTED = "inverted" CONF_IP_ADDRESS = "ip_address" CONF_IRQ_PIN = "irq_pin" @@ -443,6 +451,7 @@ CONF_MIN_FANNING_RUN_TIME = "min_fanning_run_time" CONF_MIN_HEATING_OFF_TIME = "min_heating_off_time" CONF_MIN_HEATING_RUN_TIME = "min_heating_run_time" CONF_MIN_IDLE_TIME = "min_idle_time" +CONF_MIN_IPV6_ADDR_COUNT = "min_ipv6_addr_count" CONF_MIN_LENGTH = "min_length" CONF_MIN_LEVEL = "min_level" CONF_MIN_POWER = "min_power" @@ -453,12 +462,15 @@ CONF_MIN_VALUE = "min_value" CONF_MIN_VERSION = "min_version" CONF_MINUTE = "minute" CONF_MINUTES = "minutes" +CONF_MIRROR_X = "mirror_x" +CONF_MIRROR_Y = "mirror_y" CONF_MISO_PIN = "miso_pin" CONF_MODE = "mode" CONF_MODE_COMMAND_TOPIC = "mode_command_topic" CONF_MODE_STATE_TOPIC = "mode_state_topic" CONF_MODEL = "model" CONF_MOISTURE = "moisture" +CONF_MONTH = "month" CONF_MONTHS = "months" CONF_MOSI_PIN = "mosi_pin" CONF_MOTION = "motion" @@ -484,6 +496,8 @@ CONF_NUMBER_DATAPOINT = "number_datapoint" CONF_OFF_MODE = "off_mode" CONF_OFF_SPEED_CYCLE = "off_speed_cycle" CONF_OFFSET = "offset" +CONF_OFFSET_HEIGHT = "offset_height" +CONF_OFFSET_WIDTH = "offset_width" CONF_ON = "on" CONF_ON_BLE_ADVERTISE = "on_ble_advertise" CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE = "on_ble_manufacturer_data_advertise" @@ -494,12 +508,16 @@ CONF_ON_CLIENT_CONNECTED = "on_client_connected" CONF_ON_CLIENT_DISCONNECTED = "on_client_disconnected" CONF_ON_CONNECT = "on_connect" CONF_ON_CONTROL = "on_control" +CONF_ON_DIRECTION_SET = "on_direction_set" CONF_ON_DISCONNECT = "on_disconnect" CONF_ON_DOUBLE_CLICK = "on_double_click" CONF_ON_ENROLLMENT_DONE = "on_enrollment_done" CONF_ON_ENROLLMENT_FAILED = "on_enrollment_failed" CONF_ON_ENROLLMENT_SCAN = "on_enrollment_scan" +CONF_ON_FINGER_SCAN_INVALID = "on_finger_scan_invalid" CONF_ON_FINGER_SCAN_MATCHED = "on_finger_scan_matched" +CONF_ON_FINGER_SCAN_MISPLACED = "on_finger_scan_misplaced" +CONF_ON_FINGER_SCAN_START = "on_finger_scan_start" CONF_ON_FINGER_SCAN_UNMATCHED = "on_finger_scan_unmatched" CONF_ON_JSON_MESSAGE = "on_json_message" CONF_ON_LOCK = "on_lock" @@ -507,6 +525,7 @@ CONF_ON_LOOP = "on_loop" CONF_ON_MESSAGE = "on_message" CONF_ON_MULTI_CLICK = "on_multi_click" CONF_ON_OPEN = "on_open" +CONF_ON_OSCILLATING_SET = "on_oscillating_set" CONF_ON_PRESET_SET = "on_preset_set" CONF_ON_PRESS = "on_press" CONF_ON_RAW_VALUE = "on_raw_value" @@ -523,6 +542,7 @@ CONF_ON_TOUCH = "on_touch" CONF_ON_TURN_OFF = "on_turn_off" CONF_ON_TURN_ON = "on_turn_on" CONF_ON_UNLOCK = "on_unlock" +CONF_ON_UPDATE = "on_update" CONF_ON_VALUE = "on_value" CONF_ON_VALUE_RANGE = "on_value_range" CONF_ONE = "one" @@ -717,6 +737,7 @@ CONF_SPEED_COUNT = "speed_count" CONF_SPEED_LEVEL_COMMAND_TOPIC = "speed_level_command_topic" CONF_SPEED_LEVEL_STATE_TOPIC = "speed_level_state_topic" CONF_SPEED_STATE_TOPIC = "speed_state_topic" +CONF_SPI = "spi" CONF_SPI_ID = "spi_id" CONF_SPIKE_REJECTION = "spike_rejection" CONF_SSID = "ssid" @@ -747,6 +768,7 @@ CONF_SUPPORTED_PRESETS = "supported_presets" CONF_SUPPORTED_SWING_MODES = "supported_swing_modes" CONF_SUPPORTS_COOL = "supports_cool" CONF_SUPPORTS_HEAT = "supports_heat" +CONF_SWAP_XY = "swap_xy" CONF_SWING_BOTH_ACTION = "swing_both_action" CONF_SWING_HORIZONTAL_ACTION = "swing_horizontal_action" CONF_SWING_MODE = "swing_mode" @@ -797,6 +819,7 @@ CONF_TOPIC_PREFIX = "topic_prefix" CONF_TOTAL = "total" CONF_TOTAL_POWER = "total_power" CONF_TRACES = "traces" +CONF_TRANSFORM = "transform" CONF_TRANSITION_LENGTH = "transition_length" CONF_TRIGGER_ID = "trigger_id" CONF_TRIGGER_PIN = "trigger_pin" @@ -833,6 +856,7 @@ CONF_VISUAL = "visual" CONF_VOLTAGE = "voltage" CONF_VOLTAGE_ATTENUATION = "voltage_attenuation" CONF_VOLTAGE_DIVIDER = "voltage_divider" +CONF_VOLUME = "volume" CONF_WAIT_TIME = "wait_time" CONF_WAIT_UNTIL = "wait_until" CONF_WAKEUP_PIN = "wakeup_pin" @@ -853,6 +877,7 @@ CONF_WINDOW_SIZE = "window_size" CONF_WRITE_PIN = "write_pin" CONF_X_GRID = "x_grid" CONF_Y_GRID = "y_grid" +CONF_YEAR = "year" CONF_ZERO = "zero" TYPE_GIT = "git" @@ -1068,6 +1093,7 @@ DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS = "volatile_organic_compounds_parts" DEVICE_CLASS_VOLTAGE = "voltage" DEVICE_CLASS_VOLUME = "volume" +DEVICE_CLASS_VOLUME_FLOW_RATE = "volume_flow_rate" DEVICE_CLASS_VOLUME_STORAGE = "volume_storage" DEVICE_CLASS_WATER = "water" DEVICE_CLASS_WEIGHT = "weight" diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index d82a7a5d37..a4550bcd9e 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -81,13 +81,11 @@ void Application::loop() { const uint32_t now = millis(); - if (HighFrequencyLoopRequester::is_high_frequency()) { + auto elapsed = now - this->last_loop_; + if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) { yield(); } else { - uint32_t delay_time = this->loop_interval_; - if (now - this->last_loop_ < this->loop_interval_) - delay_time = this->loop_interval_ - (now - this->last_loop_); - + uint32_t delay_time = this->loop_interval_ - elapsed; uint32_t next_schedule = this->scheduler.next_schedule_in().value_or(delay_time); // next_schedule is max 0.5*delay_time // otherwise interval=0 schedules result in constant looping with almost no sleep diff --git a/esphome/core/application.h b/esphome/core/application.h index 059e393912..26125dd935 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -39,6 +39,9 @@ #ifdef USE_NUMBER #include "esphome/components/number/number.h" #endif +#ifdef USE_DATETIME_DATE +#include "esphome/components/datetime/date_entity.h" +#endif #ifdef USE_TEXT #include "esphome/components/text/text.h" #endif @@ -121,6 +124,10 @@ class Application { void register_number(number::Number *number) { this->numbers_.push_back(number); } #endif +#ifdef USE_DATETIME_DATE + void register_date(datetime::DateEntity *date) { this->dates_.push_back(date); } +#endif + #ifdef USE_TEXT void register_text(text::Text *text) { this->texts_.push_back(text); } #endif @@ -289,6 +296,15 @@ class Application { return nullptr; } #endif +#ifdef USE_DATETIME_DATE + const std::vector &get_dates() { return this->dates_; } + datetime::DateEntity *get_date_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->dates_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif #ifdef USE_TEXT const std::vector &get_texts() { return this->texts_; } text::Text *get_text_by_key(uint32_t key, bool include_internal = false) { @@ -382,6 +398,9 @@ class Application { #ifdef USE_NUMBER std::vector numbers_{}; #endif +#ifdef USE_DATETIME_DATE + std::vector dates_{}; +#endif #ifdef USE_SELECT std::vector selects_{}; #endif diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 84c754e081..8c3da5e7e8 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -218,7 +218,7 @@ template class ActionList { /// Return the number of actions in this action list that are currently running. int num_running() { if (this->actions_begin_ == nullptr) - return false; + return 0; return this->actions_begin_->num_running_total(); } diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 50087f3efd..b0ae0aff84 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -2,6 +2,8 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/preferences.h" #include @@ -125,6 +127,27 @@ class LoopTrigger : public Trigger<>, public Component { float get_setup_priority() const override { return setup_priority::DATA; } }; +#ifdef ESPHOME_PROJECT_NAME +class ProjectUpdateTrigger : public Trigger, public Component { + public: + void setup() override { + uint32_t hash = fnv1_hash(ESPHOME_PROJECT_NAME); + ESPPreferenceObject pref = global_preferences->make_preference(hash, true); + char previous_version[30]; + char current_version[30] = ESPHOME_PROJECT_VERSION; + if (pref.load(&previous_version)) { + int cmp = strcmp(previous_version, current_version); + if (cmp < 0) { + this->trigger(previous_version); + } + } + pref.save(¤t_version); + global_preferences->sync(); + } + float get_setup_priority() const override { return setup_priority::PROCESSOR; } +}; +#endif + template class DelayAction : public Action, public Component { public: explicit DelayAction() = default; diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index e2f27f9828..594e8ff7df 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -5,6 +5,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include +#include namespace esphome { @@ -75,7 +76,12 @@ bool Component::cancel_timeout(const std::string &name) { // NOLINT void Component::call_loop() { this->loop(); } void Component::call_setup() { this->setup(); } -void Component::call_dump_config() { this->dump_config(); } +void Component::call_dump_config() { + this->dump_config(); + if (this->is_failed()) { + ESP_LOGE(TAG, " Component %s is marked FAILED", this->get_component_source()); + } +} uint32_t Component::get_component_state() const { return this->component_state_; } void Component::call() { @@ -140,18 +146,35 @@ bool Component::is_ready() { (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_SETUP; } bool Component::can_proceed() { return true; } -bool Component::status_has_warning() { return this->component_state_ & STATUS_LED_WARNING; } -bool Component::status_has_error() { return this->component_state_ & STATUS_LED_ERROR; } -void Component::status_set_warning() { +bool Component::status_has_warning() const { return this->component_state_ & STATUS_LED_WARNING; } +bool Component::status_has_error() const { return this->component_state_ & STATUS_LED_ERROR; } +void Component::status_set_warning(const char *message) { + // Don't spam the log. This risks missing different warning messages though. + if ((this->component_state_ & STATUS_LED_WARNING) != 0) + return; this->component_state_ |= STATUS_LED_WARNING; App.app_state_ |= STATUS_LED_WARNING; + ESP_LOGW(TAG, "Component %s set Warning flag: %s", this->get_component_source(), message); } -void Component::status_set_error() { +void Component::status_set_error(const char *message) { + if ((this->component_state_ & STATUS_LED_ERROR) != 0) + return; this->component_state_ |= STATUS_LED_ERROR; App.app_state_ |= STATUS_LED_ERROR; + ESP_LOGE(TAG, "Component %s set Error flag: %s", this->get_component_source(), message); +} +void Component::status_clear_warning() { + if ((this->component_state_ & STATUS_LED_WARNING) == 0) + return; + this->component_state_ &= ~STATUS_LED_WARNING; + ESP_LOGW(TAG, "Component %s cleared Warning flag", this->get_component_source()); +} +void Component::status_clear_error() { + if ((this->component_state_ & STATUS_LED_ERROR) == 0) + return; + this->component_state_ &= ~STATUS_LED_ERROR; + ESP_LOGE(TAG, "Component %s cleared Error flag", this->get_component_source()); } -void Component::status_clear_warning() { this->component_state_ &= ~STATUS_LED_WARNING; } -void Component::status_clear_error() { this->component_state_ &= ~STATUS_LED_ERROR; } void Component::status_momentary_warning(const std::string &name, uint32_t length) { this->status_set_warning(); this->set_timeout(name, length, [this]() { this->status_clear_warning(); }); @@ -169,7 +192,7 @@ float Component::get_actual_setup_priority() const { void Component::set_setup_priority(float priority) { this->setup_priority_override_ = priority; } bool Component::has_overridden_loop() const { -#ifdef CLANG_TIDY +#if defined(USE_HOST) || defined(CLANG_TIDY) bool loop_overridden = true; bool call_loop_overridden = true; #else @@ -211,8 +234,8 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() { uint32_t now = millis(); if (now - started_ > 50) { const char *src = component_ == nullptr ? "" : component_->get_component_source(); - ESP_LOGW(TAG, "Component %s took a long time for an operation (%.2f s).", src, (now - started_) / 1e3f); - ESP_LOGW(TAG, "Components should block for at most 20-30ms."); + ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms).", src, (now - started_)); + ESP_LOGW(TAG, "Components should block for at most 30 ms."); ; } } diff --git a/esphome/core/component.h b/esphome/core/component.h index 51a6296811..4f244e5fcb 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -1,8 +1,9 @@ #pragma once -#include -#include #include +#include +#include +#include #include "esphome/core/optional.h" @@ -123,13 +124,13 @@ class Component { virtual bool can_proceed(); - bool status_has_warning(); + bool status_has_warning() const; - bool status_has_error(); + bool status_has_error() const; - void status_set_warning(); + void status_set_warning(const char *message = "unspecified"); - void status_set_error(); + void status_set_error(const char *message = "unspecified"); void status_clear_warning(); @@ -192,7 +193,7 @@ class Component { * again in the future. * * The first retry of f happens after `initial_wait_time` milliseconds. The delay between retries is - * increased by multipling by `backoff_increase_factor` each time. If no backoff_increase_factor is + * increased by multiplying by `backoff_increase_factor` each time. If no backoff_increase_factor is * supplied (default = 1.0), the wait time will stay constant. * * The retry function f needs to accept a single argument: the number of attempts remaining. On the diff --git a/esphome/core/component_iterator.cpp b/esphome/core/component_iterator.cpp index 0bf8fb6f83..1e06221af6 100644 --- a/esphome/core/component_iterator.cpp +++ b/esphome/core/component_iterator.cpp @@ -202,6 +202,21 @@ void ComponentIterator::advance() { } break; #endif +#ifdef USE_DATETIME_DATE + case IteratorState::DATETIME_DATE: + if (this->at_ >= App.get_dates().size()) { + advance_platform = true; + } else { + auto *date = App.get_dates()[this->at_]; + if (date->is_internal() && !this->include_internal_) { + success = true; + break; + } else { + success = this->on_date(date); + } + } + break; +#endif #ifdef USE_TEXT case IteratorState::TEXT: if (this->at_ >= App.get_texts().size()) { diff --git a/esphome/core/component_iterator.h b/esphome/core/component_iterator.h index 646c39705f..02c6dddacb 100644 --- a/esphome/core/component_iterator.h +++ b/esphome/core/component_iterator.h @@ -57,6 +57,9 @@ class ComponentIterator { #ifdef USE_NUMBER virtual bool on_number(number::Number *number) = 0; #endif +#ifdef USE_DATETIME_DATE + virtual bool on_date(datetime::DateEntity *date) = 0; +#endif #ifdef USE_TEXT virtual bool on_text(text::Text *text) = 0; #endif @@ -114,6 +117,9 @@ class ComponentIterator { #ifdef USE_NUMBER NUMBER, #endif +#ifdef USE_DATETIME_DATE + DATETIME_DATE, +#endif #ifdef USE_TEXT TEXT, #endif diff --git a/esphome/core/config.py b/esphome/core/config.py index e4a1fdcafa..792f9da6dd 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -24,6 +24,7 @@ from esphome.const import ( CONF_ON_BOOT, CONF_ON_LOOP, CONF_ON_SHUTDOWN, + CONF_ON_UPDATE, CONF_PLATFORM, CONF_PLATFORMIO_OPTIONS, CONF_PRIORITY, @@ -38,7 +39,7 @@ from esphome.const import ( __version__ as ESPHOME_VERSION, ) from esphome.core import CORE, coroutine_with_priority -from esphome.helpers import copy_file_if_changed, walk_files +from esphome.helpers import copy_file_if_changed, get_str_env, walk_files _LOGGER = logging.getLogger(__name__) @@ -52,6 +53,9 @@ ShutdownTrigger = cg.esphome_ns.class_( LoopTrigger = cg.esphome_ns.class_( "LoopTrigger", cg.Component, automation.Trigger.template() ) +ProjectUpdateTrigger = cg.esphome_ns.class_( + "ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string) +) VERSION_REGEX = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[ab]\d+)?$") @@ -102,16 +106,6 @@ def valid_project_name(value: str): return value -def validate_version(value: str): - min_version = cv.Version.parse(value) - current_version = cv.Version.parse(ESPHOME_VERSION) - if current_version < min_version: - raise cv.Invalid( - f"Your ESPHome version is too old. Please update to at least {min_version}" - ) - return value - - if "ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT" in os.environ: _compile_process_limit_default = min( int(os.environ["ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT"]), @@ -161,10 +155,17 @@ CONFIG_SCHEMA = cv.All( cv.string_strict, valid_project_name ), cv.Required(CONF_VERSION): cv.string_strict, + cv.Optional(CONF_ON_UPDATE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + ProjectUpdateTrigger + ), + } + ), } ), cv.Optional(CONF_MIN_VERSION, default=ESPHOME_VERSION): cv.All( - cv.version_number, validate_version + cv.version_number, cv.validate_esphome_version ), cv.Optional( CONF_COMPILE_PROCESS_LIMIT, default=_compile_process_limit_default @@ -200,7 +201,8 @@ def preload_core_config(config, result): CORE.data[KEY_CORE] = {} if CONF_BUILD_PATH not in conf: - conf[CONF_BUILD_PATH] = f"build/{CORE.name}" + build_path = get_str_env("ESPHOME_BUILD_PATH", "build") + conf[CONF_BUILD_PATH] = os.path.join(build_path, CORE.name) CORE.build_path = CORE.relative_internal_path(conf[CONF_BUILD_PATH]) has_oldstyle = CONF_PLATFORM in conf @@ -389,9 +391,15 @@ async def to_code(config): if config[CONF_INCLUDES]: CORE.add_job(add_includes, config[CONF_INCLUDES]) - if CONF_PROJECT in config: - cg.add_define("ESPHOME_PROJECT_NAME", config[CONF_PROJECT][CONF_NAME]) - cg.add_define("ESPHOME_PROJECT_VERSION", config[CONF_PROJECT][CONF_VERSION]) + if project_conf := config.get(CONF_PROJECT): + cg.add_define("ESPHOME_PROJECT_NAME", project_conf[CONF_NAME]) + cg.add_define("ESPHOME_PROJECT_VERSION", project_conf[CONF_VERSION]) + for conf in project_conf.get(CONF_ON_UPDATE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + await cg.register_component(trigger, conf) + await automation.build_automation( + trigger, [(cg.std_string, "version")], conf + ) if config[CONF_PLATFORMIO_OPTIONS]: CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp index 95f63b6224..43b8fea50c 100644 --- a/esphome/core/controller.cpp +++ b/esphome/core/controller.cpp @@ -1,6 +1,6 @@ #include "controller.h" -#include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/log.h" namespace esphome { @@ -59,6 +59,12 @@ void Controller::setup_controller(bool include_internal) { obj->add_on_state_callback([this, obj](float state) { this->on_number_update(obj, state); }); } #endif +#ifdef USE_DATETIME_DATE + for (auto *obj : App.get_dates()) { + if (include_internal || !obj->is_internal()) + obj->add_on_state_callback([this, obj]() { this->on_date_update(obj); }); + } +#endif #ifdef USE_TEXT for (auto *obj : App.get_texts()) { if (include_internal || !obj->is_internal()) diff --git a/esphome/core/controller.h b/esphome/core/controller.h index f977d8a36a..c31cd22d07 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -31,6 +31,9 @@ #ifdef USE_NUMBER #include "esphome/components/number/number.h" #endif +#ifdef USE_DATETIME_DATE +#include "esphome/components/datetime/date_entity.h" +#endif #ifdef USE_TEXT #include "esphome/components/text/text.h" #endif @@ -79,6 +82,9 @@ class Controller { #ifdef USE_NUMBER virtual void on_number_update(number::Number *obj, float state){}; #endif +#ifdef USE_DATETIME_DATE + virtual void on_date_update(datetime::DateEntity *obj){}; +#endif #ifdef USE_TEXT virtual void on_text_update(text::Text *obj, const std::string &state){}; #endif diff --git a/esphome/core/defines.h b/esphome/core/defines.h index e75abdb88f..501dccc6fa 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -34,9 +34,12 @@ #define USE_MEDIA_PLAYER #define USE_MQTT #define USE_NUMBER +#define USE_DATETIME +#define USE_DATETIME_DATE #define USE_OTA #define USE_OTA_PASSWORD #define USE_OTA_STATE_CALLBACK +#define USE_OTA_VERSION 1 #define USE_OUTPUT #define USE_POWER_SUPPLY #define USE_QR_CODE @@ -95,7 +98,7 @@ // ESP8266-specific feature flags #ifdef USE_ESP8266 #define USE_ADC_SENSOR_VCC -#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 0, 2) +#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 1, 2) #define USE_ESP8266_PREFERENCES_FLASH #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_SOCKET_IMPL_LWIP_TCP diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index c95c0470de..0f7afc6a4e 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -11,6 +11,14 @@ #include #include +#ifdef USE_HOST +#ifndef _WIN32 +#include +#include +#include +#endif +#include +#endif #if defined(USE_ESP8266) #include #include @@ -415,7 +423,7 @@ std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { int8_t step_to_accuracy_decimals(float step) { // use printf %g to find number of digits based on temperature step char buf[32]; - sprintf(buf, "%.5g", step); + snprintf(buf, sizeof buf, "%.5g", step); std::string str{buf}; size_t dot_pos = str.find('.'); @@ -551,7 +559,10 @@ void HighFrequencyLoopRequester::stop() { bool HighFrequencyLoopRequester::is_high_frequency() { return num_requests > 0; } void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) -#if defined(USE_ESP32) +#if defined(USE_HOST) + static const uint8_t esphome_host_mac_address[6] = USE_ESPHOME_HOST_MAC_ADDRESS; + memcpy(mac, esphome_host_mac_address, sizeof(esphome_host_mac_address)); +#elif defined(USE_ESP32) #if defined(CONFIG_SOC_IEEE802154_SUPPORTED) || defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC) // When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default // returns the 802.15.4 EUI-64 address. Read directly from eFuse instead. @@ -569,6 +580,8 @@ void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parame WiFi.macAddress(mac); #elif defined(USE_LIBRETINY) WiFi.macAddress(mac); +#else +// this should be an error, but that messes with CI checks. #error No mac address method defined #endif } std::string get_mac_address() { diff --git a/esphome/core/ring_buffer.cpp b/esphome/core/ring_buffer.cpp new file mode 100644 index 0000000000..9bd3d9d853 --- /dev/null +++ b/esphome/core/ring_buffer.cpp @@ -0,0 +1,50 @@ +#include "ring_buffer.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +#include "helpers.h" + +namespace esphome { + +static const char *const TAG = "ring_buffer"; + +std::unique_ptr RingBuffer::create(size_t len) { + std::unique_ptr rb = make_unique(); + + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + rb->storage_ = allocator.allocate(len + 1); + if (rb->storage_ == nullptr) { + return nullptr; + } + + rb->handle_ = xStreamBufferCreateStatic(len + 1, 0, rb->storage_, &rb->structure_); + ESP_LOGD(TAG, "Created ring buffer with size %u", len); + return rb; +} + +size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) { + return xStreamBufferReceive(this->handle_, data, len, ticks_to_wait); +} + +size_t RingBuffer::write(void *data, size_t len) { + size_t free = this->free(); + if (free < len) { + size_t needed = len - free; + uint8_t discard[needed]; + xStreamBufferReceive(this->handle_, discard, needed, 0); + } + return xStreamBufferSend(this->handle_, data, len, 0); +} + +size_t RingBuffer::available() const { return xStreamBufferBytesAvailable(this->handle_); } + +size_t RingBuffer::free() const { return xStreamBufferSpacesAvailable(this->handle_); } + +BaseType_t RingBuffer::reset() { return xStreamBufferReset(this->handle_); } + +} // namespace esphome + +#endif diff --git a/esphome/core/ring_buffer.h b/esphome/core/ring_buffer.h new file mode 100644 index 0000000000..e602068844 --- /dev/null +++ b/esphome/core/ring_buffer.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef USE_ESP32 + +#include +#include + +#include +#include + +namespace esphome { + +class RingBuffer { + public: + size_t read(void *data, size_t len, TickType_t ticks_to_wait = 0); + + size_t write(void *data, size_t len); + + size_t available() const; + size_t free() const; + + BaseType_t reset(); + + static std::unique_ptr create(size_t len); + + protected: + StreamBufferHandle_t handle_; + StaticStreamBuffer_t structure_; + uint8_t *storage_; +}; + +} // namespace esphome + +#endif diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index 751b2a2703..0004fc7e8e 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -1,10 +1,13 @@ #include "time.h" // NOLINT +#include "helpers.h" + +#include namespace esphome { -static bool is_leap_year(uint32_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); } +bool is_leap_year(uint32_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); } -static uint8_t days_in_month(uint8_t month, uint16_t year) { +uint8_t days_in_month(uint8_t month, uint16_t year) { static const uint8_t DAYS_IN_MONTH[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; uint8_t days = DAYS_IN_MONTH[month]; if (month == 2 && is_leap_year(year)) @@ -61,6 +64,47 @@ std::string ESPTime::strftime(const std::string &format) { return timestr; } +bool ESPTime::strptime(const std::string &time_to_parse, ESPTime &esp_time) { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + int num; + + if (sscanf(time_to_parse.c_str(), "%04hu-%02hhu-%02hhu %02hhu:%02hhu:%02hhu %n", &year, &month, &day, // NOLINT + &hour, // NOLINT + &minute, // NOLINT + &second, &num) == 6 && // NOLINT + num == time_to_parse.size()) { + esp_time.year = year; + esp_time.month = month; + esp_time.day_of_month = day; + esp_time.hour = hour; + esp_time.minute = minute; + esp_time.second = second; + } else if (sscanf(time_to_parse.c_str(), "%02hhu:%02hhu:%02hhu %n", &hour, &minute, &second, &num) == 3 && // NOLINT + num == time_to_parse.size()) { + esp_time.hour = hour; + esp_time.minute = minute; + esp_time.second = second; + } else if (sscanf(time_to_parse.c_str(), "%02hhu:%02hhu %n", &hour, &minute, &num) == 2 && // NOLINT + num == time_to_parse.size()) { + esp_time.hour = hour; + esp_time.minute = minute; + esp_time.second = 0; + } else if (sscanf(time_to_parse.c_str(), "%04hu-%02hhu-%02hhu %n", &year, &month, &day, &num) == 3 && // NOLINT + num == time_to_parse.size()) { + esp_time.year = year; + esp_time.month = month; + esp_time.day_of_month = day; + } else { + return false; + } + return true; +} + void ESPTime::increment_second() { this->timestamp++; if (!increment_time_value(this->second, 0, 60)) diff --git a/esphome/core/time.h b/esphome/core/time.h index 14c36311e0..4300cf26b7 100644 --- a/esphome/core/time.h +++ b/esphome/core/time.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -8,6 +9,10 @@ namespace esphome { template bool increment_time_value(T ¤t, uint16_t begin, uint16_t end); +bool is_leap_year(uint32_t year); + +uint8_t days_in_month(uint8_t month, uint16_t year); + /// A more user-friendly version of struct tm from time.h struct ESPTime { /** seconds after the minute [0-60] @@ -62,6 +67,13 @@ struct ESPTime { this->day_of_year < 367 && this->month > 0 && this->month < 13; } + /** Convert a string to ESPTime struct as specified by the format argument. + * @param time_to_parse null-terminated c string formatet like this: 2020-08-25 05:30:00. + * @param esp_time an instance of a ESPTime struct + * @return the success sate of the parsing + */ + static bool strptime(const std::string &time_to_parse, ESPTime &esp_time); + /// Convert a C tm struct instance with a C unix epoch timestamp to an ESPTime instance. static ESPTime from_c_tm(struct tm *c_tm, time_t c_time); diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 909a786917..04616d97c2 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -478,7 +478,7 @@ def variable( :param type_: Manually define a type for the variable, only use this when it's not possible to do so during config validation phase (for example because of template arguments). - :returns The new variable as a MockObj. + :return: The new variable as a MockObj. """ assert isinstance(id_, ID) rhs = safe_exp(rhs) @@ -526,7 +526,7 @@ def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj :param type_: Manually define a type for the variable, only use this when it's not possible to do so during config validation phase (for example because of template arguments). - :returns The new variable as a MockObj. + :return: The new variable as a MockObj. """ assert isinstance(id_, ID) rhs = safe_exp(rhs) @@ -549,7 +549,7 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": :param type_: Manually define a type for the variable, only use this when it's not possible to do so during config validation phase (for example because of template arguments). - :returns The new variable as a MockObj. + :return: The new variable as a MockObj. """ rhs = safe_exp(rhs) obj = MockObj(id_, "->") @@ -570,7 +570,7 @@ def new_Pvariable(id_: ID, *args: SafeExpType) -> Pvariable: :param id_: The ID used to declare the variable (also specifies the type). :param args: The values to pass to the constructor. - :returns The new variable as a MockObj. + :return: The new variable as a MockObj. """ if args and isinstance(args[0], TemplateArguments): id_ = id_.copy() diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 7d0e386b66..0f1b7f236b 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -38,3 +38,4 @@ gpio_ns = esphome_ns.namespace("gpio") gpio_Flags = gpio_ns.enum("Flags", is_class=True) EntityCategory = esphome_ns.enum("EntityCategory") Parented = esphome_ns.class_("Parented") +ESPTime = esphome_ns.struct("ESPTime") diff --git a/esphome/dashboard/const.py b/esphome/dashboard/const.py index ed2b81d3e8..db66cb5ead 100644 --- a/esphome/dashboard/const.py +++ b/esphome/dashboard/const.py @@ -4,5 +4,9 @@ EVENT_ENTRY_ADDED = "entry_added" EVENT_ENTRY_REMOVED = "entry_removed" EVENT_ENTRY_UPDATED = "entry_updated" EVENT_ENTRY_STATE_CHANGED = "entry_state_changed" +MAX_EXECUTOR_WORKERS = 48 + SENTINEL = object() + +DASHBOARD_COMMAND = ["esphome", "--dashboard"] diff --git a/esphome/dashboard/core.py b/esphome/dashboard/core.py index ffec9784e8..875ff6b91f 100644 --- a/esphome/dashboard/core.py +++ b/esphome/dashboard/core.py @@ -1,13 +1,16 @@ from __future__ import annotations import asyncio +import contextlib import logging import threading from dataclasses import dataclass from functools import partial from typing import TYPE_CHECKING, Any, Callable +from collections.abc import Coroutine from ..zeroconf import DiscoveredImport +from .dns import DNSCache from .entries import DashboardEntries from .settings import DashboardSettings @@ -69,6 +72,8 @@ class ESPHomeDashboard: "mqtt_ping_request", "mdns_status", "settings", + "dns_cache", + "_background_tasks", ) def __init__(self) -> None: @@ -81,7 +86,9 @@ class ESPHomeDashboard: self.ping_request: asyncio.Event | None = None self.mqtt_ping_request = threading.Event() self.mdns_status: MDNSStatus | None = None - self.settings: DashboardSettings = DashboardSettings() + self.settings = DashboardSettings() + self.dns_cache = DNSCache() + self._background_tasks: set[asyncio.Task] = set() async def async_setup(self) -> None: """Setup the dashboard.""" @@ -129,7 +136,19 @@ class ESPHomeDashboard: if settings.status_use_mqtt: status_thread_mqtt.join() self.mqtt_ping_request.set() + for task in self._background_tasks: + task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await task await asyncio.sleep(0) + def async_create_background_task( + self, coro: Coroutine[Any, Any, Any] + ) -> asyncio.Task: + """Create a background task.""" + task = self.loop.create_task(coro) + task.add_done_callback(self._background_tasks.discard) + return task + DASHBOARD = ESPHomeDashboard() diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 789b14653c..2be98ab3e4 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -1,11 +1,19 @@ from __future__ import annotations import asyncio +import logging import os import socket +import threading +import traceback +from asyncio import events +from concurrent.futures import ThreadPoolExecutor +from time import monotonic +from typing import Any from esphome.storage_json import EsphomeStorageJSON, esphome_storage_path +from .const import MAX_EXECUTOR_WORKERS from .core import DASHBOARD from .web_server import make_app, start_web_server @@ -14,6 +22,95 @@ ENV_DEV = "ESPHOME_DASHBOARD_DEV" settings = DASHBOARD.settings +def can_use_pidfd() -> bool: + """Check if pidfd_open is available. + + Back ported from cpython 3.12 + """ + if not hasattr(os, "pidfd_open"): + return False + try: + pid = os.getpid() + os.close(os.pidfd_open(pid, 0)) + except OSError: + # blocked by security policy like SECCOMP + return False + return True + + +class DashboardEventLoopPolicy(asyncio.DefaultEventLoopPolicy): + """Event loop policy for Home Assistant.""" + + def __init__(self, debug: bool) -> None: + """Init the event loop policy.""" + super().__init__() + self.debug = debug + self._watcher: asyncio.AbstractChildWatcher | None = None + + def _init_watcher(self) -> None: + """Initialize the watcher for child processes. + + Back ported from cpython 3.12 + """ + with events._lock: # type: ignore[attr-defined] # pylint: disable=protected-access + if self._watcher is None: # pragma: no branch + if can_use_pidfd(): + self._watcher = asyncio.PidfdChildWatcher() + else: + self._watcher = asyncio.ThreadedChildWatcher() + if threading.current_thread() is threading.main_thread(): + self._watcher.attach_loop( + self._local._loop # type: ignore[attr-defined] # pylint: disable=protected-access + ) + + @property + def loop_name(self) -> str: + """Return name of the loop.""" + return self._loop_factory.__name__ # type: ignore[no-any-return,attr-defined] + + def new_event_loop(self) -> asyncio.AbstractEventLoop: + """Get the event loop.""" + loop: asyncio.AbstractEventLoop = super().new_event_loop() + loop.set_exception_handler(_async_loop_exception_handler) + + if self.debug: + loop.set_debug(True) + + executor = ThreadPoolExecutor( + thread_name_prefix="SyncWorker", max_workers=MAX_EXECUTOR_WORKERS + ) + loop.set_default_executor(executor) + # bind the built-in time.monotonic directly as loop.time to avoid the + # overhead of the additional method call since its the most called loop + # method and its roughly 10%+ of all the call time in base_events.py + loop.time = monotonic # type: ignore[method-assign] + return loop + + +def _async_loop_exception_handler(_: Any, context: dict[str, Any]) -> None: + """Handle all exception inside the core loop.""" + kwargs = {} + if exception := context.get("exception"): + kwargs["exc_info"] = (type(exception), exception, exception.__traceback__) + + logger = logging.getLogger(__package__) + if source_traceback := context.get("source_traceback"): + stack_summary = "".join(traceback.format_list(source_traceback)) + logger.error( + "Error doing job: %s: %s", + context["message"], + stack_summary, + **kwargs, # type: ignore[arg-type] + ) + return + + logger.error( + "Error doing job: %s", + context["message"], + **kwargs, # type: ignore[arg-type] + ) + + def start_dashboard(args) -> None: """Start the dashboard.""" settings.parse_args(args) @@ -26,6 +123,8 @@ def start_dashboard(args) -> None: storage.save(path) settings.cookie_secret = storage.cookie_secret + asyncio.set_event_loop_policy(DashboardEventLoopPolicy(settings.verbose)) + try: asyncio.run(async_start(args)) except KeyboardInterrupt: diff --git a/esphome/dashboard/dns.py b/esphome/dashboard/dns.py new file mode 100644 index 0000000000..b78a909220 --- /dev/null +++ b/esphome/dashboard/dns.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import asyncio +import sys + +from icmplib import NameLookupError, async_resolve + +if sys.version_info >= (3, 11): + from asyncio import timeout as async_timeout +else: + from async_timeout import timeout as async_timeout + + +async def _async_resolve_wrapper(hostname: str) -> list[str] | Exception: + """Wrap the icmplib async_resolve function.""" + try: + async with async_timeout(2): + return await async_resolve(hostname) + except (asyncio.TimeoutError, NameLookupError, UnicodeError) as ex: + return ex + + +class DNSCache: + """DNS cache for the dashboard.""" + + def __init__(self, ttl: int | None = 120) -> None: + """Initialize the DNSCache.""" + self._cache: dict[str, tuple[float, list[str] | Exception]] = {} + self._ttl = ttl + + async def async_resolve( + self, hostname: str, now_monotonic: float + ) -> list[str] | Exception: + """Resolve a hostname to a list of IP address.""" + if expire_time_addresses := self._cache.get(hostname): + expire_time, addresses = expire_time_addresses + if expire_time > now_monotonic: + return addresses + + expires = now_monotonic + self._ttl + addresses = await _async_resolve_wrapper(hostname) + self._cache[hostname] = (expires, addresses) + return addresses diff --git a/esphome/dashboard/entries.py b/esphome/dashboard/entries.py index ad139b830b..cd318ba8a7 100644 --- a/esphome/dashboard/entries.py +++ b/esphome/dashboard/entries.py @@ -10,12 +10,14 @@ from esphome import const, util from esphome.storage_json import StorageJSON, ext_storage_path from .const import ( + DASHBOARD_COMMAND, EVENT_ENTRY_ADDED, EVENT_ENTRY_REMOVED, EVENT_ENTRY_STATE_CHANGED, EVENT_ENTRY_UPDATED, ) from .enum import StrEnum +from .util.subprocess import async_run_system_command if TYPE_CHECKING: from .core import ESPHomeDashboard @@ -235,6 +237,14 @@ class DashboardEntries: ) return path_to_cache_key + def async_schedule_storage_json_update(self, filename: str) -> None: + """Schedule a task to update the storage JSON file.""" + self._dashboard.async_create_background_task( + async_run_system_command( + [*DASHBOARD_COMMAND, "compile", "--only-generate", filename] + ) + ) + class DashboardEntry: """Represents a single dashboard entry. diff --git a/esphome/dashboard/enum.py b/esphome/dashboard/enum.py index 6aff21620e..0fe30cf92a 100644 --- a/esphome/dashboard/enum.py +++ b/esphome/dashboard/enum.py @@ -1,4 +1,5 @@ """Enum backports from standard lib.""" + from __future__ import annotations from enum import Enum diff --git a/esphome/dashboard/settings.py b/esphome/dashboard/settings.py index 1a5b1620e8..1f05abab4c 100644 --- a/esphome/dashboard/settings.py +++ b/esphome/dashboard/settings.py @@ -14,7 +14,19 @@ from .util.password import password_hash class DashboardSettings: """Settings for the dashboard.""" + __slots__ = ( + "config_dir", + "password_hash", + "username", + "using_password", + "on_ha_addon", + "cookie_secret", + "absolute_config_dir", + "verbose", + ) + def __init__(self) -> None: + """Initialize the dashboard settings.""" self.config_dir: str = "" self.password_hash: str = "" self.username: str = "" @@ -22,8 +34,10 @@ class DashboardSettings: self.on_ha_addon: bool = False self.cookie_secret: str | None = None self.absolute_config_dir: Path | None = None + self.verbose: bool = False def parse_args(self, args: Any) -> None: + """Parse the arguments.""" self.on_ha_addon: bool = args.ha_addon password = args.password or os.getenv("PASSWORD") or "" if not self.on_ha_addon: @@ -33,6 +47,7 @@ class DashboardSettings: self.password_hash = password_hash(password) self.config_dir = args.configuration self.absolute_config_dir = Path(self.config_dir).resolve() + self.verbose = args.verbose CORE.config_path = os.path.join(self.config_dir, ".") @property diff --git a/esphome/dashboard/status/ping.py b/esphome/dashboard/status/ping.py index d8281d9de1..6630f03c9d 100644 --- a/esphome/dashboard/status/ping.py +++ b/esphome/dashboard/status/ping.py @@ -1,20 +1,20 @@ from __future__ import annotations import asyncio -import os +import logging +import time from typing import cast +from icmplib import Host, SocketPermissionError, async_ping + +from ..const import MAX_EXECUTOR_WORKERS from ..core import DASHBOARD -from ..entries import DashboardEntry, bool_to_entry_state +from ..entries import DashboardEntry, EntryState, bool_to_entry_state from ..util.itertools import chunked -from ..util.subprocess import async_system_command_status +_LOGGER = logging.getLogger(__name__) -async def _async_ping_host(host: str) -> bool: - """Ping a host.""" - return await async_system_command_status( - ["ping", "-n" if os.name == "nt" else "-c", "1", host] - ) +GROUP_SIZE = int(MAX_EXECUTOR_WORKERS / 2) class PingStatus: @@ -27,23 +27,81 @@ class PingStatus: """Run the ping status.""" dashboard = DASHBOARD entries = dashboard.entries + privileged = await _can_use_icmp_lib_with_privilege() + if privileged is None: + _LOGGER.warning("Cannot use icmplib because privileges are insufficient") + return while not dashboard.stop_event.is_set(): # Only ping if the dashboard is open await dashboard.ping_request.wait() + dashboard.ping_request.clear() current_entries = dashboard.entries.async_all() to_ping: list[DashboardEntry] = [ entry for entry in current_entries if entry.address is not None ] - for ping_group in chunked(to_ping, 16): + + # Resolve DNS for all entries + entries_with_addresses: dict[DashboardEntry, list[str]] = {} + for ping_group in chunked(to_ping, GROUP_SIZE): ping_group = cast(list[DashboardEntry], ping_group) - results = await asyncio.gather( - *(_async_ping_host(entry.address) for entry in ping_group), + now_monotonic = time.monotonic() + dns_results = await asyncio.gather( + *( + dashboard.dns_cache.async_resolve(entry.address, now_monotonic) + for entry in ping_group + ), return_exceptions=True, ) - for entry, result in zip(ping_group, results): + + for entry, result in zip(ping_group, dns_results): if isinstance(result, Exception): - result = False + entries.async_set_state(entry, EntryState.UNKNOWN) + continue + if isinstance(result, BaseException): + raise result + entries_with_addresses[entry] = result + + # Ping all entries with valid addresses + for ping_group in chunked(entries_with_addresses.items(), GROUP_SIZE): + entry_addresses = cast(tuple[DashboardEntry, list[str]], ping_group) + + results = await asyncio.gather( + *( + async_ping(addresses[0], privileged=privileged) + for _, addresses in entry_addresses + ), + return_exceptions=True, + ) + + for entry_addresses, result in zip(entry_addresses, results): + if isinstance(result, Exception): + ping_result = False elif isinstance(result, BaseException): raise result - entries.async_set_state(entry, bool_to_entry_state(result)) + else: + host: Host = result + ping_result = host.is_alive + entry, _ = entry_addresses + entries.async_set_state(entry, bool_to_entry_state(ping_result)) + + +async def _can_use_icmp_lib_with_privilege() -> None | bool: + """Verify we can create a raw socket.""" + try: + await async_ping("127.0.0.1", count=0, timeout=0, privileged=True) + except SocketPermissionError: + try: + await async_ping("127.0.0.1", count=0, timeout=0, privileged=False) + except SocketPermissionError: + _LOGGER.debug( + "Cannot use icmplib because privileges are insufficient to create the" + " socket" + ) + return None + + _LOGGER.debug("Using icmplib in privileged=False mode") + return False + + _LOGGER.debug("Using icmplib in privileged=True mode") + return True diff --git a/esphome/dashboard/util/file.py b/esphome/dashboard/util/file.py index 5f3c5f5f1b..661d5f34cf 100644 --- a/esphome/dashboard/util/file.py +++ b/esphome/dashboard/util/file.py @@ -30,6 +30,7 @@ def write_file( """ tmp_filename = "" + missing_fchmod = False try: # Modern versions of Python tempfile create this file with mode 0o600 with tempfile.NamedTemporaryFile( @@ -38,8 +39,15 @@ def write_file( fdesc.write(utf8_data) tmp_filename = fdesc.name if not private: - os.fchmod(fdesc.fileno(), 0o644) + try: + os.fchmod(fdesc.fileno(), 0o644) + except AttributeError: + # os.fchmod is not available on Windows + missing_fchmod = True + os.replace(tmp_filename, filename) + if missing_fchmod: + os.chmod(filename, 0o644) finally: if os.path.exists(tmp_filename): try: diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index 4552aebf7b..9ee2312781 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -13,6 +13,7 @@ import secrets import shutil import subprocess import threading +import time from collections.abc import Iterable from pathlib import Path from typing import TYPE_CHECKING, Any, Callable, TypeVar @@ -39,6 +40,7 @@ from esphome.storage_json import StorageJSON, ext_storage_path, trash_storage_pa from esphome.util import get_serial_ports, shlex_quote from esphome.yaml_util import FastestAvailableSafeLoader +from .const import DASHBOARD_COMMAND from .core import DASHBOARD from .entries import EntryState, entry_state_to_bool from .util.file import write_file @@ -285,9 +287,6 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler): raise NotImplementedError -DASHBOARD_COMMAND = ["esphome", "--dashboard"] - - class EsphomePortCommandWebSocket(EsphomeCommandWebSocket): """Base class for commands that require a port.""" @@ -301,12 +300,29 @@ class EsphomePortCommandWebSocket(EsphomeCommandWebSocket): config_file = settings.rel_path(configuration) port = json_message["port"] if ( - port == "OTA" - and (mdns := dashboard.mdns_status) + port == "OTA" # pylint: disable=too-many-boolean-expressions and (entry := entries.get(config_file)) - and (address := await mdns.async_resolve_host(entry.name)) + and entry.loaded_integrations + and "api" in entry.loaded_integrations ): - port = address + if (mdns := dashboard.mdns_status) and ( + address := await mdns.async_resolve_host(entry.name) + ): + # Use the IP address if available but only + # if the API is loaded and the device is online + # since MQTT logging will not work otherwise + port = address + elif ( + entry.address + and ( + address_list := await dashboard.dns_cache.async_resolve( + entry.address, time.monotonic() + ) + ) + and not isinstance(address_list, Exception) + ): + # If mdns is not available, try to use the DNS cache + port = address_list[0] return [ *DASHBOARD_COMMAND, @@ -500,7 +516,8 @@ class ImportRequestHandler(BaseHandler): self.set_status(500) self.write("File already exists") return - except ValueError: + except ValueError as e: + _LOGGER.error(e) self.set_status(422) self.write("Invalid package url") return @@ -671,6 +688,11 @@ class MainRequestHandler(BaseHandler): @authenticated def get(self) -> None: begin = bool(self.get_argument("begin", False)) + if settings.using_password: + # Simply accessing the xsrf_token sets the cookie for us + self.xsrf_token # pylint: disable=pointless-statement + else: + self.clear_cookie("_xsrf") self.render( "index.template.html", @@ -790,15 +812,33 @@ class EditRequestHandler(BaseHandler): @bind_config async def get(self, configuration: str | None = None) -> None: """Get the content of a file.""" - loop = asyncio.get_running_loop() - filename = settings.rel_path(configuration) - content = await loop.run_in_executor(None, self._read_file, filename) - self.write(content) + if not configuration.endswith((".yaml", ".yml")): + self.send_error(404) + return - def _read_file(self, filename: str) -> bytes: + filename = settings.rel_path(configuration) + if Path(filename).resolve().parent != settings.absolute_config_dir: + self.send_error(404) + return + + loop = asyncio.get_running_loop() + content = await loop.run_in_executor( + None, self._read_file, filename, configuration + ) + if content is not None: + self.set_header("Content-Type", "application/yaml") + self.write(content) + + def _read_file(self, filename: str, configuration: str) -> bytes | None: """Read a file and return the content as bytes.""" - with open(file=filename, encoding="utf-8") as f: - return f.read() + try: + with open(file=filename, encoding="utf-8") as f: + return f.read() + except FileNotFoundError: + if configuration in const.SECRETS_FILES: + return "" + self.set_status(404) + return None def _write_file(self, filename: str, content: bytes) -> None: """Write a file with the given content.""" @@ -808,15 +848,19 @@ class EditRequestHandler(BaseHandler): @bind_config async def post(self, configuration: str | None = None) -> None: """Write the content of a file.""" + if not configuration.endswith((".yaml", ".yml")): + self.send_error(404) + return + + filename = settings.rel_path(configuration) + if Path(filename).resolve().parent != settings.absolute_config_dir: + self.send_error(404) + return + loop = asyncio.get_running_loop() - config_file = settings.rel_path(configuration) - await loop.run_in_executor( - None, self._write_file, config_file, self.request.body - ) + await loop.run_in_executor(None, self._write_file, filename, self.request.body) # Ensure the StorageJSON is updated as well - await async_run_system_command( - [*DASHBOARD_COMMAND, "compile", "--only-generate", config_file] - ) + DASHBOARD.entries.async_schedule_storage_json_update(filename) self.set_status(200) @@ -1063,6 +1107,7 @@ def make_app(debug=get_bool_env(ENV_DEV)) -> tornado.web.Application: "log_function": log_function, "websocket_ping_interval": 30.0, "template_path": get_base_frontend_path(), + "xsrf_cookies": settings.using_password, } rel = settings.relative_url return tornado.web.Application( diff --git a/esphome/espota2.py b/esphome/espota2.py index dbf48a989a..580536153a 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -12,32 +12,34 @@ import time from esphome.core import EsphomeError from esphome.helpers import is_ip_address, resolve_ip_address -RESPONSE_OK = 0 -RESPONSE_REQUEST_AUTH = 1 +RESPONSE_OK = 0x00 +RESPONSE_REQUEST_AUTH = 0x01 -RESPONSE_HEADER_OK = 64 -RESPONSE_AUTH_OK = 65 -RESPONSE_UPDATE_PREPARE_OK = 66 -RESPONSE_BIN_MD5_OK = 67 -RESPONSE_RECEIVE_OK = 68 -RESPONSE_UPDATE_END_OK = 69 -RESPONSE_SUPPORTS_COMPRESSION = 70 +RESPONSE_HEADER_OK = 0x40 +RESPONSE_AUTH_OK = 0x41 +RESPONSE_UPDATE_PREPARE_OK = 0x42 +RESPONSE_BIN_MD5_OK = 0x43 +RESPONSE_RECEIVE_OK = 0x44 +RESPONSE_UPDATE_END_OK = 0x45 +RESPONSE_SUPPORTS_COMPRESSION = 0x46 +RESPONSE_CHUNK_OK = 0x47 -RESPONSE_ERROR_MAGIC = 128 -RESPONSE_ERROR_UPDATE_PREPARE = 129 -RESPONSE_ERROR_AUTH_INVALID = 130 -RESPONSE_ERROR_WRITING_FLASH = 131 -RESPONSE_ERROR_UPDATE_END = 132 -RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 133 -RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 134 -RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135 -RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136 -RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137 -RESPONSE_ERROR_NO_UPDATE_PARTITION = 138 -RESPONSE_ERROR_MD5_MISMATCH = 139 -RESPONSE_ERROR_UNKNOWN = 255 +RESPONSE_ERROR_MAGIC = 0x80 +RESPONSE_ERROR_UPDATE_PREPARE = 0x81 +RESPONSE_ERROR_AUTH_INVALID = 0x82 +RESPONSE_ERROR_WRITING_FLASH = 0x83 +RESPONSE_ERROR_UPDATE_END = 0x84 +RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 0x85 +RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 0x86 +RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 0x87 +RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 0x88 +RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 0x89 +RESPONSE_ERROR_NO_UPDATE_PARTITION = 0x8A +RESPONSE_ERROR_MD5_MISMATCH = 0x8B +RESPONSE_ERROR_UNKNOWN = 0xFF OTA_VERSION_1_0 = 1 +OTA_VERSION_2_0 = 2 MAGIC_BYTES = [0x6C, 0x26, 0xF7, 0x5C, 0x45] @@ -203,8 +205,12 @@ def perform_ota( send_check(sock, MAGIC_BYTES, "magic bytes") _, version = receive_exactly(sock, 2, "version", RESPONSE_OK) - if version != OTA_VERSION_1_0: - raise OTAError(f"Unsupported OTA version {version}") + _LOGGER.debug("Device support OTA version: %s", version) + supported_versions = (OTA_VERSION_1_0, OTA_VERSION_2_0) + if version not in supported_versions: + raise OTAError( + f"Device uses unsupported OTA version {version}, this ESPHome supports {supported_versions}" + ) # Features send_check(sock, FEATURE_SUPPORTS_COMPRESSION, "features") @@ -279,6 +285,8 @@ def perform_ota( try: sock.sendall(chunk) + if version >= OTA_VERSION_2_0: + receive_exactly(sock, 1, "chunk OK", RESPONSE_CHUNK_OK) except OSError as err: sys.stderr.write("\n") raise OTAError(f"Error sending data: {err}") from err diff --git a/esphome/external_files.py b/esphome/external_files.py index 5b476286f3..a1422d02b1 100644 --- a/esphome/external_files.py +++ b/esphome/external_files.py @@ -33,7 +33,9 @@ def has_remote_file_changed(url, local_file_path): IF_MODIFIED_SINCE: local_modification_time_str, CACHE_CONTROL: CACHE_CONTROL_MAX_AGE + "3600", } - response = requests.head(url, headers=headers, timeout=NETWORK_TIMEOUT) + response = requests.head( + url, headers=headers, timeout=NETWORK_TIMEOUT, allow_redirects=True + ) _LOGGER.debug( "has_remote_file_changed: File %s, Local modified %s, response code %d", diff --git a/esphome/git.py b/esphome/git.py index 4f0911233e..e41777f425 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -59,17 +59,14 @@ def clone_or_update( ) repo_dir = _compute_destination_path(key, domain) - fetch_pr_branch = ref is not None and ref.startswith("pull/") if not repo_dir.is_dir(): _LOGGER.info("Cloning %s", key) _LOGGER.debug("Location: %s", repo_dir) cmd = ["git", "clone", "--depth=1"] - if ref is not None and not fetch_pr_branch: - cmd += ["--branch", ref] cmd += ["--", url, str(repo_dir)] run_git_command(cmd) - if fetch_pr_branch: + if ref is not None: # We need to fetch the PR branch first, otherwise git will complain # about missing objects _LOGGER.info("Fetching %s", ref) diff --git a/esphome/helpers.py b/esphome/helpers.py index 00416b591f..4c8cb4e2cc 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -3,6 +3,7 @@ from contextlib import suppress import logging import os +import platform from pathlib import Path from typing import Union import tempfile @@ -11,6 +12,10 @@ import re _LOGGER = logging.getLogger(__name__) +IS_MACOS = platform.system() == "Darwin" +IS_WINDOWS = platform.system() == "Windows" +IS_LINUX = platform.system() == "Linux" + def ensure_unique_string(preferred_string, current_strings): test_string = preferred_string @@ -357,7 +362,7 @@ def snake_case(value): return value.replace(" ", "_").lower() -_DISALLOWED_CHARS = re.compile(r"[^a-zA-Z0-9_]") +_DISALLOWED_CHARS = re.compile(r"[^a-zA-Z0-9-_]") def sanitize(value): diff --git a/esphome/loader.py b/esphome/loader.py index cd21e5a509..40a38d0a14 100644 --- a/esphome/loader.py +++ b/esphome/loader.py @@ -57,6 +57,10 @@ class ComponentManifest: def multi_conf(self) -> bool: return getattr(self.module, "MULTI_CONF", False) + @property + def multi_conf_no_default(self) -> bool: + return getattr(self.module, "MULTI_CONF_NO_DEFAULT", False) + @property def to_code(self) -> Optional[Callable[[Any], None]]: return getattr(self.module, "to_code", None) diff --git a/esphome/pins.py b/esphome/pins.py index e2fd8e98e2..d02ad357a0 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -1,7 +1,7 @@ import operator from functools import reduce import esphome.config_validation as cv -from esphome.core import CORE, ID +from esphome.core import CORE from esphome.const import ( CONF_INPUT, @@ -25,15 +25,16 @@ class PinRegistry(dict): def reset(self): self.pins_used = {} - def get_count(self, key, number): + def get_count(self, key, id, number): """ Get the number of places a given pin is used. - :param key: The ID of the defining component + :param key: The key of the registered pin schema. + :param id: The ID of the defining component :param number: The pin number :return: The number of places the pin is used. """ - pin_key = (key, number) - return self.pins_used[pin_key] if pin_key in self.pins_used else 0 + pin_key = (key, id, number) + return len(self.pins_used[pin_key]) if pin_key in self.pins_used else 0 def register(self, name, schema, final_validate=None): """ @@ -65,9 +66,10 @@ class PinRegistry(dict): result = self[key][1](conf) if CONF_NUMBER in result: # key maps to the pin schema - if isinstance(key, ID): - key = key.id - pin_key = (key, result[CONF_NUMBER]) + if key != CORE.target_platform: + pin_key = (key, conf[key], result[CONF_NUMBER]) + else: + pin_key = (key, key, result[CONF_NUMBER]) if pin_key not in self.pins_used: self.pins_used[pin_key] = [] # client_id identifies the instance of the providing component @@ -101,7 +103,7 @@ class PinRegistry(dict): Run the final validation for all pins, and check for reuse :param fconf: The full config """ - for (key, _), pin_list in self.pins_used.items(): + for (key, _, _), pin_list in self.pins_used.items(): count = len(pin_list) # number of places same pin used. final_val_fun = self[key][2] # final validation function for pin_path, client_id, pin_config in pin_list: @@ -309,10 +311,18 @@ def gpio_base_schema( map(lambda m: (cv.Optional(m, default=mode_default), cv.boolean), modes) ) + def _number_validator(value): + if isinstance(value, str) and value.upper().startswith("GPIOX"): + raise cv.Invalid( + f"Found placeholder '{value}' when expecting a GPIO pin number.\n" + "You must replace this with an actual pin number." + ) + return number_validator(value) + schema = cv.Schema( { cv.GenerateID(): cv.declare_id(pin_type), - cv.Required(CONF_NUMBER): number_validator, + cv.Required(CONF_NUMBER): _number_validator, cv.Optional(CONF_ALLOW_OTHER_USES): cv.boolean, cv.Optional(CONF_MODE, default={}): cv.All(mode_dict, mode_validator), } diff --git a/esphome/schema_extractors.py b/esphome/schema_extractors.py index 2280a84849..5491bc88c4 100644 --- a/esphome/schema_extractors.py +++ b/esphome/schema_extractors.py @@ -8,7 +8,6 @@ originally do. However there is a property to further disable decorator impact.""" - # This is set to true by script/build_language_schema.py # only, so data is collected (again functionality is not modified) EnableSchemaExtraction = False diff --git a/esphome/types.py b/esphome/types.py index adb16fa91b..27ec61ceff 100644 --- a/esphome/types.py +++ b/esphome/types.py @@ -1,4 +1,5 @@ """This helper module tracks commonly used types in the esphome python codebase.""" + from typing import Union from esphome.core import ID, Lambda, EsphomeCore diff --git a/esphome/voluptuous_schema.py b/esphome/voluptuous_schema.py index e2171cabed..9af6cb717c 100644 --- a/esphome/voluptuous_schema.py +++ b/esphome/voluptuous_schema.py @@ -64,7 +64,7 @@ class _Schema(vol.Schema): # Recursively compile schema _compiled_schema = {} - for skey, svalue in vol.iteritems(schema): + for skey, svalue in schema.items(): new_key = self._compile(skey) new_value = self._compile(svalue) _compiled_schema[skey] = (new_key, new_value) diff --git a/esphome/vscode.py b/esphome/vscode.py index cb2f51976f..8198d2659a 100644 --- a/esphome/vscode.py +++ b/esphome/vscode.py @@ -1,20 +1,22 @@ +from __future__ import annotations import json import os +from io import StringIO +from typing import Any -from typing import Optional - -from esphome.config import load_config, _format_vol_invalid, Config +from esphome.yaml_util import parse_yaml +from esphome.config import validate_config, _format_vol_invalid, Config from esphome.core import CORE, DocumentRange import esphome.config_validation as cv -def _get_invalid_range(res: Config, invalid: cv.Invalid) -> Optional[DocumentRange]: +def _get_invalid_range(res: Config, invalid: cv.Invalid) -> DocumentRange | None: return res.get_deepest_document_range_for_path( invalid.path, invalid.error_message == "extra keys not allowed" ) -def _dump_range(range: Optional[DocumentRange]) -> Optional[dict]: +def _dump_range(range: DocumentRange | None) -> dict | None: if range is None: return None return { @@ -56,6 +58,25 @@ class VSCodeResult: ) +def _read_file_content_from_json_on_stdin() -> str: + """Read the content of a json encoded file from stdin.""" + data = json.loads(input()) + assert data["type"] == "file_response" + return data["content"] + + +def _print_file_read_event(path: str) -> None: + """Print a file read event.""" + print( + json.dumps( + { + "type": "read_file", + "path": path, + } + ) + ) + + def read_config(args): while True: CORE.reset() @@ -68,9 +89,17 @@ def read_config(args): CORE.config_path = os.path.join(args.configuration, f) else: CORE.config_path = data["file"] + + file_name = CORE.config_path + _print_file_read_event(file_name) + raw_yaml = _read_file_content_from_json_on_stdin() + command_line_substitutions: dict[str, Any] = ( + dict(args.substitution) if args.substitution else {} + ) vs = VSCodeResult() try: - res = load_config(dict(args.substitution) if args.substitution else {}) + config = parse_yaml(file_name, StringIO(raw_yaml)) + res = validate_config(config, command_line_substitutions) except Exception as err: # pylint: disable=broad-except vs.add_yaml_error(str(err)) else: diff --git a/esphome/wizard.py b/esphome/wizard.py index 1308338ad0..4ec366bbb9 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -51,10 +51,12 @@ BASE_CONFIG_FRIENDLY = """esphome: friendly_name: {friendly_name} """ -LOGGER_API_CONFIG = """ +LOGGER_CONFIG = """ # Enable logging logger: +""" +API_CONFIG = """ # Enable Home Assistant API api: """ @@ -136,7 +138,12 @@ def wizard_file(**kwargs): config += HARDWARE_BASE_CONFIGS[kwargs["platform"]].format(**kwargs) - config += LOGGER_API_CONFIG + config += LOGGER_CONFIG + + if kwargs["board"] == "rpipico": + return config + + config += API_CONFIG # Configure API if "password" in kwargs: diff --git a/esphome/writer.py b/esphome/writer.py index 83e95614a6..3ad0e60d31 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -4,7 +4,7 @@ import re from pathlib import Path from typing import Union -from esphome.config import iter_components +from esphome.config import iter_components, iter_component_configs from esphome.const import ( HEADER_FILE_EXTENSIONS, SOURCE_FILE_EXTENSIONS, @@ -70,14 +70,14 @@ UPLOAD_SPEED_OVERRIDE = { def get_flags(key): flags = set() - for _, component, conf in iter_components(CORE.config): + for _, component, conf in iter_component_configs(CORE.config): flags |= getattr(component, key)(conf) return flags def get_include_text(): include_text = '#include "esphome.h"\nusing namespace esphome;\n' - for _, component, conf in iter_components(CORE.config): + for _, component, conf in iter_component_configs(CORE.config): if not hasattr(component, "includes"): continue includes = component.includes @@ -232,7 +232,7 @@ the custom_components folder or the external_components feature. def copy_src_tree(): source_files: list[loader.FileResource] = [] - for _, component, _ in iter_components(CORE.config): + for _, component in iter_components(CORE.config): source_files += component.resources source_files_map = { Path(x.package.replace(".", "/") + "/" + x.resource): x for x in source_files diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index f0f755dd61..c7aa78201f 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -1,36 +1,38 @@ +from __future__ import annotations + import fnmatch import functools import inspect import logging import math import os - import uuid +from io import TextIOWrapper +from typing import Any + import yaml import yaml.constructor +from yaml import SafeLoader as PurePythonLoader + +try: + from yaml import CSafeLoader as FastestAvailableSafeLoader +except ImportError: + FastestAvailableSafeLoader = PurePythonLoader from esphome import core -from esphome.config_helpers import read_config_file, Extend, Remove +from esphome.config_helpers import Extend, Remove from esphome.core import ( + CORE, + DocumentRange, EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, - DocumentRange, - CORE, ) from esphome.helpers import add_class_to_obj from esphome.util import OrderedDict, filter_yaml_files -try: - from yaml import CSafeLoader as FastestAvailableSafeLoader -except ImportError: - from yaml import ( # type: ignore[assignment] - SafeLoader as FastestAvailableSafeLoader, - ) - - _LOGGER = logging.getLogger(__name__) # Mostly copied from Home Assistant because that code works fine and @@ -97,7 +99,7 @@ def _add_data_ref(fn): return wrapped -class ESPHomeLoader(FastestAvailableSafeLoader): +class ESPHomeLoaderMixin: """Loader class that keeps track of line numbers.""" @_add_data_ref @@ -282,8 +284,8 @@ class ESPHomeLoader(FastestAvailableSafeLoader): return file, vars def substitute_vars(config, vars): - from esphome.const import CONF_SUBSTITUTIONS from esphome.components import substitutions + from esphome.const import CONF_DEFAULTS, CONF_SUBSTITUTIONS org_subs = None result = config @@ -294,7 +296,15 @@ class ESPHomeLoader(FastestAvailableSafeLoader): elif CONF_SUBSTITUTIONS in result: org_subs = result.pop(CONF_SUBSTITUTIONS) + defaults = {} + if CONF_DEFAULTS in result: + defaults = result.pop(CONF_DEFAULTS) + result[CONF_SUBSTITUTIONS] = vars + for k, v in defaults.items(): + if k not in result[CONF_SUBSTITUTIONS]: + result[CONF_SUBSTITUTIONS][k] = v + # Ignore missing vars that refer to the top level substitutions substitutions.do_substitution_pass(result, None, ignore_missing=True) result.pop(CONF_SUBSTITUTIONS) @@ -367,50 +377,76 @@ class ESPHomeLoader(FastestAvailableSafeLoader): return Remove(str(node.value)) -ESPHomeLoader.add_constructor("tag:yaml.org,2002:int", ESPHomeLoader.construct_yaml_int) -ESPHomeLoader.add_constructor( - "tag:yaml.org,2002:float", ESPHomeLoader.construct_yaml_float -) -ESPHomeLoader.add_constructor( - "tag:yaml.org,2002:binary", ESPHomeLoader.construct_yaml_binary -) -ESPHomeLoader.add_constructor( - "tag:yaml.org,2002:omap", ESPHomeLoader.construct_yaml_omap -) -ESPHomeLoader.add_constructor("tag:yaml.org,2002:str", ESPHomeLoader.construct_yaml_str) -ESPHomeLoader.add_constructor("tag:yaml.org,2002:seq", ESPHomeLoader.construct_yaml_seq) -ESPHomeLoader.add_constructor("tag:yaml.org,2002:map", ESPHomeLoader.construct_yaml_map) -ESPHomeLoader.add_constructor("!env_var", ESPHomeLoader.construct_env_var) -ESPHomeLoader.add_constructor("!secret", ESPHomeLoader.construct_secret) -ESPHomeLoader.add_constructor("!include", ESPHomeLoader.construct_include) -ESPHomeLoader.add_constructor( - "!include_dir_list", ESPHomeLoader.construct_include_dir_list -) -ESPHomeLoader.add_constructor( - "!include_dir_merge_list", ESPHomeLoader.construct_include_dir_merge_list -) -ESPHomeLoader.add_constructor( - "!include_dir_named", ESPHomeLoader.construct_include_dir_named -) -ESPHomeLoader.add_constructor( - "!include_dir_merge_named", ESPHomeLoader.construct_include_dir_merge_named -) -ESPHomeLoader.add_constructor("!lambda", ESPHomeLoader.construct_lambda) -ESPHomeLoader.add_constructor("!force", ESPHomeLoader.construct_force) -ESPHomeLoader.add_constructor("!extend", ESPHomeLoader.construct_extend) -ESPHomeLoader.add_constructor("!remove", ESPHomeLoader.construct_remove) +class ESPHomeLoader(ESPHomeLoaderMixin, FastestAvailableSafeLoader): + """Loader class that keeps track of line numbers.""" -def load_yaml(fname, clear_secrets=True): +class ESPHomePurePythonLoader(ESPHomeLoaderMixin, PurePythonLoader): + """Loader class that keeps track of line numbers.""" + + +for _loader in (ESPHomeLoader, ESPHomePurePythonLoader): + _loader.add_constructor("tag:yaml.org,2002:int", _loader.construct_yaml_int) + _loader.add_constructor("tag:yaml.org,2002:float", _loader.construct_yaml_float) + _loader.add_constructor("tag:yaml.org,2002:binary", _loader.construct_yaml_binary) + _loader.add_constructor("tag:yaml.org,2002:omap", _loader.construct_yaml_omap) + _loader.add_constructor("tag:yaml.org,2002:str", _loader.construct_yaml_str) + _loader.add_constructor("tag:yaml.org,2002:seq", _loader.construct_yaml_seq) + _loader.add_constructor("tag:yaml.org,2002:map", _loader.construct_yaml_map) + _loader.add_constructor("!env_var", _loader.construct_env_var) + _loader.add_constructor("!secret", _loader.construct_secret) + _loader.add_constructor("!include", _loader.construct_include) + _loader.add_constructor("!include_dir_list", _loader.construct_include_dir_list) + _loader.add_constructor( + "!include_dir_merge_list", _loader.construct_include_dir_merge_list + ) + _loader.add_constructor("!include_dir_named", _loader.construct_include_dir_named) + _loader.add_constructor( + "!include_dir_merge_named", _loader.construct_include_dir_merge_named + ) + _loader.add_constructor("!lambda", _loader.construct_lambda) + _loader.add_constructor("!force", _loader.construct_force) + _loader.add_constructor("!extend", _loader.construct_extend) + _loader.add_constructor("!remove", _loader.construct_remove) + + +def load_yaml(fname: str, clear_secrets: bool = True) -> Any: if clear_secrets: _SECRET_VALUES.clear() _SECRET_CACHE.clear() return _load_yaml_internal(fname) -def _load_yaml_internal(fname): - content = read_config_file(fname) - loader = ESPHomeLoader(content) +def parse_yaml(file_name: str, file_handle: TextIOWrapper) -> Any: + """Parse a YAML file.""" + try: + return _load_yaml_internal_with_type(ESPHomeLoader, file_name, file_handle) + except EsphomeError: + # Loading failed, so we now load with the Python loader which has more + # readable exceptions + # Rewind the stream so we can try again + file_handle.seek(0, 0) + return _load_yaml_internal_with_type( + ESPHomePurePythonLoader, file_name, file_handle + ) + + +def _load_yaml_internal(fname: str) -> Any: + """Load a YAML file.""" + try: + with open(fname, encoding="utf-8") as f_handle: + return parse_yaml(fname, f_handle) + except (UnicodeDecodeError, OSError) as err: + raise EsphomeError(f"Error reading file {fname}: {err}") from err + + +def _load_yaml_internal_with_type( + loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader], + fname: str, + content: TextIOWrapper, +) -> Any: + """Load a YAML file.""" + loader = loader_type(content) loader.name = fname try: return loader.get_single_data() or OrderedDict() diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index 72cc4c00c6..b67ea41323 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -110,7 +110,7 @@ class DashboardImportDiscovery: self, zeroconf: Zeroconf, info: AsyncServiceInfo, service_type: str, name: str ) -> None: """Process a service info.""" - if await info.async_request(zeroconf): + if await info.async_request(zeroconf, timeout=3000): self._process_service_info(name, info) def _process_service_info(self, name: str, info: ServiceInfo) -> None: diff --git a/platformio.ini b/platformio.ini index 68c4220aab..5fedd14086 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,7 +39,7 @@ lib_deps = bblanchon/ArduinoJson@6.18.5 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 - pavlodn/HaierProtocol@0.9.24 ; haier + pavlodn/HaierProtocol@0.9.25 ; haier ; This is using the repository until a new release is published to PlatformIO https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library build_flags = @@ -80,9 +80,9 @@ build_flags = ; This are common settings for the ESP8266 using Arduino. [common:esp8266-arduino] extends = common:arduino -platform = platformio/espressif8266@3.2.0 +platform = platformio/espressif8266@4.2.1 platform_packages = - platformio/framework-arduinoespressif8266@~3.30002.0 + platformio/framework-arduinoespressif8266@~3.30102.0 framework = arduino lib_deps = @@ -93,7 +93,8 @@ lib_deps = ESP8266HTTPClient ; http_request (Arduino built-in) ESP8266mDNS ; mdns (Arduino built-in) DNSServer ; captive_portal (Arduino built-in) - crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir + crankyoldgit/IRremoteESP8266@~2.8.4 ; heatpumpir + droscy/esp_wireguard@0.4.0 ; wireguard build_flags = ${common:arduino.build_flags} -Wno-nonnull-compare @@ -116,14 +117,14 @@ lib_deps = WiFi ; wifi,web_server_base,ethernet (Arduino built-in) Update ; ota,web_server_base (Arduino built-in) ${common:arduino.lib_deps} - esphome/AsyncTCP-esphome@1.2.2 ; async_tcp + esphome/AsyncTCP-esphome@2.1.3 ; async_tcp WiFiClientSecure ; http_request,nextion (Arduino built-in) HTTPClient ; http_request,nextion (Arduino built-in) ESPmDNS ; mdns (Arduino built-in) DNSServer ; captive_portal (Arduino built-in) esphome/ESP32-audioI2S@2.0.7 ; i2s_audio - crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir - droscy/esp_wireguard@0.3.2 ; wireguard + crankyoldgit/IRremoteESP8266@~2.8.4 ; heatpumpir + droscy/esp_wireguard@0.4.0 ; wireguard build_flags = ${common:arduino.build_flags} -DUSE_ESP32 @@ -136,13 +137,13 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script extends = common:idf platform = platformio/espressif32@5.4.0 platform_packages = - platformio/framework-espidf@~3.40405.0 + platformio/framework-espidf@~3.40406.0 framework = espidf lib_deps = ${common:idf.lib_deps} espressif/esp32-camera@1.0.0 ; esp32_camera - droscy/esp_wireguard@0.3.2 ; wireguard + droscy/esp_wireguard@0.4.0 ; wireguard build_flags = ${common:idf.build_flags} -Wno-nonnull-compare @@ -153,13 +154,12 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script ; These are common settings for the RP2040 using Arduino. [common:rp2040-arduino] extends = common:arduino -board_build.core = earlephilhower board_build.filesystem_size = 0.5m platform = https://github.com/maxgerhardt/platform-raspberrypi.git platform_packages = ; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted - earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.6.0/rp2040-3.6.0.zip + earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.7.2/rp2040-3.7.2.zip framework = arduino lib_deps = @@ -388,3 +388,4 @@ lib_deps = build_flags = ${common.build_flags} -DUSE_HOST + -std=c++17 diff --git a/requirements.txt b/requirements.txt index 115f85de3e..702127eca8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,22 @@ -voluptuous==0.14.1 +async_timeout==4.0.3; python_version <= "3.10" +cryptography==42.0.2 +voluptuous==0.14.2 PyYAML==6.0.1 paho-mqtt==1.6.1 colorama==0.4.6 +icmplib==3.0.4 tornado==6.4 tzlocal==5.2 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==6.1.11 # When updating platformio, also update Dockerfile +platformio==6.1.13 # When updating platformio, also update Dockerfile esptool==4.7.0 click==8.1.7 -esphome-dashboard==20231107.0 -aioesphomeapi==21.0.1 +esphome-dashboard==20240319.0 +aioesphomeapi==23.2.0 zeroconf==0.131.0 python-magic==0.4.27 +ruamel.yaml==0.18.6 # dashboard_import # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000000..6bfa015c6f --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,4 @@ +# Useful stuff when working in a development environment +clang-format==13.0.1 +clang-tidy==14.0.6 +yamllint==1.35.1 diff --git a/requirements_optional.txt b/requirements_optional.txt index bc4ea08c92..c984d41332 100644 --- a/requirements_optional.txt +++ b/requirements_optional.txt @@ -1,3 +1,2 @@ -pillow==10.1.0 +pillow==10.2.0 cairosvg==2.7.1 -cryptography==41.0.4 diff --git a/requirements_test.txt b/requirements_test.txt index 18c6dedf3e..8fb59683b4 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,15 +1,13 @@ pylint==3.0.3 -flake8==6.1.0 # also change in .pre-commit-config.yaml when updating -black==23.12.0 # also change in .pre-commit-config.yaml when updating -pyupgrade==3.15.0 # also change in .pre-commit-config.yaml when updating +flake8==7.0.0 # also change in .pre-commit-config.yaml when updating +black==24.2.0 # also change in .pre-commit-config.yaml when updating +pyupgrade==3.15.1 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==7.4.3 +pytest==8.1.1 pytest-cov==4.1.0 pytest-mock==3.12.0 -pytest-asyncio==0.23.2 +pytest-asyncio==0.23.6 asyncmock==0.4.2 -hypothesis==5.49.0 - -clang-format==13.0.1 ; platform_machine != 'armv7l' +hypothesis==6.92.1 diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index b1292095d8..a2bc3abf64 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -17,28 +17,22 @@ then run this script with python3 and the files will be generated, they still need to be formatted """ -import re import os +import re +import sys +from abc import ABC, abstractmethod from pathlib import Path -from textwrap import dedent from subprocess import call +from textwrap import dedent # Generate with # protoc --python_out=script/api_protobuf -I esphome/components/api/ api_options.proto - import aioesphomeapi.api_options_pb2 as pb import google.protobuf.descriptor_pb2 as descriptor -file_header = "// This file was automatically generated with a tool.\n" -file_header += "// See scripts/api_protobuf/api_protobuf.py\n" - -cwd = Path(__file__).resolve().parent -root = cwd.parent.parent / "esphome" / "components" / "api" -prot = root / "api.protoc" -call(["protoc", "-o", str(prot), "-I", str(root), "api.proto"]) -content = prot.read_bytes() - -d = descriptor.FileDescriptorSet.FromString(content) +FILE_HEADER = """// This file was automatically generated with a tool. +// See scripts/api_protobuf/api_protobuf.py +""" def indent_list(text, padding=" "): @@ -64,7 +58,7 @@ def camel_to_snake(name): return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() -class TypeInfo: +class TypeInfo(ABC): def __init__(self, field): self._field = field @@ -186,10 +180,12 @@ class TypeInfo: def dump_content(self): o = f'out.append(" {self.name}: ");\n' o += self.dump(f"this->{self.field_name}") + "\n" - o += f'out.append("\\n");\n' + o += 'out.append("\\n");\n' return o - dump = None + @abstractmethod + def dump(self, name: str): + pass TYPE_INFO = {} @@ -212,7 +208,7 @@ class DoubleType(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%g", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -225,7 +221,7 @@ class FloatType(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%g", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -238,7 +234,7 @@ class Int64Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%lld", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -251,7 +247,7 @@ class UInt64Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%llu", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -264,7 +260,7 @@ class Int32Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%" PRId32, {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -277,7 +273,7 @@ class Fixed64Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%llu", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -290,7 +286,7 @@ class Fixed32Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%" PRIu32, {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -372,7 +368,7 @@ class UInt32Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%" PRIu32, {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -406,7 +402,7 @@ class SFixed32Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%" PRId32, {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -419,7 +415,7 @@ class SFixed64Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%lld", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -432,7 +428,7 @@ class SInt32Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%" PRId32, {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -445,7 +441,7 @@ class SInt64Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%lld", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -527,7 +523,7 @@ class RepeatedTypeInfo(TypeInfo): def encode_content(self): o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n" - o += f"}}" + o += "}" return o @property @@ -535,10 +531,13 @@ class RepeatedTypeInfo(TypeInfo): o = f'for (const auto {"" if self._ti_is_bool else "&"}it : this->{self.field_name}) {{\n' o += f' out.append(" {self.name}: ");\n' o += indent(self._ti.dump("it")) + "\n" - o += f' out.append("\\n");\n' - o += f"}}\n" + o += ' out.append("\\n");\n' + o += "}\n" return o + def dump(self, _: str): + pass + def build_enum_type(desc): name = desc.name @@ -547,17 +546,17 @@ def build_enum_type(desc): out += f" {v.name} = {v.number},\n" out += "};\n" - cpp = f"#ifdef HAS_PROTO_MESSAGE_DUMP\n" + cpp = "#ifdef HAS_PROTO_MESSAGE_DUMP\n" cpp += f"template<> const char *proto_enum_to_string(enums::{name} value) {{\n" - cpp += f" switch (value) {{\n" + cpp += " switch (value) {\n" for v in desc.value: cpp += f" case enums::{v.name}:\n" cpp += f' return "{v.name}";\n' - cpp += f" default:\n" - cpp += f' return "UNKNOWN";\n' - cpp += f" }}\n" - cpp += f"}}\n" - cpp += f"#endif\n" + cpp += " default:\n" + cpp += ' return "UNKNOWN";\n' + cpp += " }\n" + cpp += "}\n" + cpp += "#endif\n" return out, cpp @@ -652,10 +651,10 @@ def build_message_type(desc): o += f" {dump[0]} " else: o += "\n" - o += f" __attribute__((unused)) char buffer[64];\n" + o += " __attribute__((unused)) char buffer[64];\n" o += f' out.append("{desc.name} {{\\n");\n' o += indent("\n".join(dump)) + "\n" - o += f' out.append("}}");\n' + o += ' out.append("}");\n' else: o2 = f'out.append("{desc.name} {{}}");' if len(o) + len(o2) + 3 < 120: @@ -664,9 +663,9 @@ def build_message_type(desc): o += "\n" o += f" {o2}\n" o += "}\n" - cpp += f"#ifdef HAS_PROTO_MESSAGE_DUMP\n" + cpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n" cpp += o - cpp += f"#endif\n" + cpp += "#endif\n" prot = "#ifdef HAS_PROTO_MESSAGE_DUMP\n" prot += "void dump_to(std::string &out) const override;\n" prot += "#endif\n" @@ -684,71 +683,12 @@ def build_message_type(desc): return out, cpp -file = d.file[0] -content = file_header -content += """\ -#pragma once - -#include "proto.h" - -namespace esphome { -namespace api { - -""" - -cpp = file_header -cpp += """\ -#include "api_pb2.h" -#include "esphome/core/log.h" - -#include - -namespace esphome { -namespace api { - -""" - -content += "namespace enums {\n\n" - -for enum in file.enum_type: - s, c = build_enum_type(enum) - content += s - cpp += c - -content += "\n} // namespace enums\n\n" - -mt = file.message_type - -for m in mt: - s, c = build_message_type(m) - content += s - cpp += c - -content += """\ - -} // namespace api -} // namespace esphome -""" -cpp += """\ - -} // namespace api -} // namespace esphome -""" - -with open(root / "api_pb2.h", "w") as f: - f.write(content) - -with open(root / "api_pb2.cpp", "w") as f: - f.write(cpp) - SOURCE_BOTH = 0 SOURCE_SERVER = 1 SOURCE_CLIENT = 2 RECEIVE_CASES = {} -class_name = "APIServerConnectionBase" - ifdefs = {} @@ -768,7 +708,6 @@ def build_service_message_type(mt): ifdef = get_opt(mt, pb.ifdef) log = get_opt(mt, pb.log, True) - nodelay = get_opt(mt, pb.no_delay, False) hout = "" cout = "" @@ -781,14 +720,14 @@ def build_service_message_type(mt): # Generate send func = f"send_{snake}" hout += f"bool {func}(const {mt.name} &msg);\n" - cout += f"bool {class_name}::{func}(const {mt.name} &msg) {{\n" + cout += f"bool APIServerConnectionBase::{func}(const {mt.name} &msg) {{\n" if log: - cout += f"#ifdef HAS_PROTO_MESSAGE_DUMP\n" + cout += "#ifdef HAS_PROTO_MESSAGE_DUMP\n" cout += f' ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' - cout += f"#endif\n" + cout += "#endif\n" # cout += f' this->set_nodelay({str(nodelay).lower()});\n' cout += f" return this->send_message_<{mt.name}>(msg, {id_});\n" - cout += f"}}\n" + cout += "}\n" if source in (SOURCE_BOTH, SOURCE_CLIENT): # Generate receive func = f"on_{snake}" @@ -797,169 +736,242 @@ def build_service_message_type(mt): if ifdef is not None: case += f"#ifdef {ifdef}\n" case += f"{mt.name} msg;\n" - case += f"msg.decode(msg_data, msg_size);\n" + case += "msg.decode(msg_data, msg_size);\n" if log: - case += f"#ifdef HAS_PROTO_MESSAGE_DUMP\n" + case += "#ifdef HAS_PROTO_MESSAGE_DUMP\n" case += f'ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' - case += f"#endif\n" + case += "#endif\n" case += f"this->{func}(msg);\n" if ifdef is not None: - case += f"#endif\n" + case += "#endif\n" case += "break;" RECEIVE_CASES[id_] = case if ifdef is not None: - hout += f"#endif\n" - cout += f"#endif\n" + hout += "#endif\n" + cout += "#endif\n" return hout, cout -hpp = file_header -hpp += """\ -#pragma once +def main(): + cwd = Path(__file__).resolve().parent + root = cwd.parent.parent / "esphome" / "components" / "api" + prot_file = root / "api.protoc" + call(["protoc", "-o", str(prot_file), "-I", str(root), "api.proto"]) + proto_content = prot_file.read_bytes() -#include "api_pb2.h" -#include "esphome/core/defines.h" + # pylint: disable-next=no-member + d = descriptor.FileDescriptorSet.FromString(proto_content) -namespace esphome { -namespace api { + file = d.file[0] + content = FILE_HEADER + content += """\ + #pragma once -""" + #include "proto.h" -cpp = file_header -cpp += """\ -#include "api_pb2_service.h" -#include "esphome/core/log.h" + namespace esphome { + namespace api { -namespace esphome { -namespace api { + """ -static const char *const TAG = "api.service"; + cpp = FILE_HEADER + cpp += """\ + #include "api_pb2.h" + #include "esphome/core/log.h" -""" + #include -hpp += f"class {class_name} : public ProtoService {{\n" -hpp += " public:\n" + namespace esphome { + namespace api { -for mt in file.message_type: - obj = build_service_message_type(mt) - if obj is None: - continue - hout, cout = obj - hpp += indent(hout) + "\n" - cpp += cout + """ -cases = list(RECEIVE_CASES.items()) -cases.sort() -hpp += " protected:\n" -hpp += f" bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n" -out = f"bool {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n" -out += f" switch (msg_type) {{\n" -for i, case in cases: - c = f"case {i}: {{\n" - c += indent(case) + "\n" - c += f"}}" - out += indent(c, " ") + "\n" -out += " default:\n" -out += " return false;\n" -out += " }\n" -out += " return true;\n" -out += "}\n" -cpp += out -hpp += "};\n" + content += "namespace enums {\n\n" -serv = file.service[0] -class_name = "APIServerConnection" -hpp += "\n" -hpp += f"class {class_name} : public {class_name}Base {{\n" -hpp += " public:\n" -hpp_protected = "" -cpp += "\n" + for enum in file.enum_type: + s, c = build_enum_type(enum) + content += s + cpp += c -m = serv.method[0] -for m in serv.method: - func = m.name - inp = m.input_type[1:] - ret = m.output_type[1:] - is_void = ret == "void" - snake = camel_to_snake(inp) - on_func = f"on_{snake}" - needs_conn = get_opt(m, pb.needs_setup_connection, True) - needs_auth = get_opt(m, pb.needs_authentication, True) + content += "\n} // namespace enums\n\n" - ifdef = ifdefs.get(inp, None) + mt = file.message_type - if ifdef is not None: - hpp += f"#ifdef {ifdef}\n" - hpp_protected += f"#ifdef {ifdef}\n" - cpp += f"#ifdef {ifdef}\n" + for m in mt: + s, c = build_message_type(m) + content += s + cpp += c - hpp_protected += f" void {on_func}(const {inp} &msg) override;\n" - hpp += f" virtual {ret} {func}(const {inp} &msg) = 0;\n" - cpp += f"void {class_name}::{on_func}(const {inp} &msg) {{\n" - body = "" - if needs_conn: - body += "if (!this->is_connection_setup()) {\n" - body += " this->on_no_setup_connection();\n" - body += " return;\n" - body += "}\n" - if needs_auth: - body += "if (!this->is_authenticated()) {\n" - body += " this->on_unauthenticated_access();\n" - body += " return;\n" - body += "}\n" + content += """\ - if is_void: - body += f"this->{func}(msg);\n" - else: - body += f"{ret} ret = this->{func}(msg);\n" - ret_snake = camel_to_snake(ret) - body += f"if (!this->send_{ret_snake}(ret)) {{\n" - body += f" this->on_fatal_error();\n" - body += "}\n" - cpp += indent(body) + "\n" + "}\n" + } // namespace api + } // namespace esphome + """ + cpp += """\ - if ifdef is not None: - hpp += f"#endif\n" - hpp_protected += f"#endif\n" - cpp += f"#endif\n" + } // namespace api + } // namespace esphome + """ -hpp += " protected:\n" -hpp += hpp_protected -hpp += "};\n" + with open(root / "api_pb2.h", "w", encoding="utf-8") as f: + f.write(content) -hpp += """\ + with open(root / "api_pb2.cpp", "w", encoding="utf-8") as f: + f.write(cpp) -} // namespace api -} // namespace esphome -""" -cpp += """\ + hpp = FILE_HEADER + hpp += """\ + #pragma once -} // namespace api -} // namespace esphome -""" + #include "api_pb2.h" + #include "esphome/core/defines.h" -with open(root / "api_pb2_service.h", "w") as f: - f.write(hpp) + namespace esphome { + namespace api { -with open(root / "api_pb2_service.cpp", "w") as f: - f.write(cpp) + """ -prot.unlink() + cpp = FILE_HEADER + cpp += """\ + #include "api_pb2_service.h" + #include "esphome/core/log.h" -try: - import clang_format + namespace esphome { + namespace api { - def exec_clang_format(path): - clang_format_path = os.path.join( - os.path.dirname(clang_format.__file__), "data", "bin", "clang-format" - ) - call([clang_format_path, "-i", path]) + static const char *const TAG = "api.service"; - exec_clang_format(root / "api_pb2_service.h") - exec_clang_format(root / "api_pb2_service.cpp") - exec_clang_format(root / "api_pb2.h") - exec_clang_format(root / "api_pb2.cpp") -except ImportError: - pass + """ + + class_name = "APIServerConnectionBase" + + hpp += f"class {class_name} : public ProtoService {{\n" + hpp += " public:\n" + + for mt in file.message_type: + obj = build_service_message_type(mt) + if obj is None: + continue + hout, cout = obj + hpp += indent(hout) + "\n" + cpp += cout + + cases = list(RECEIVE_CASES.items()) + cases.sort() + hpp += " protected:\n" + hpp += " bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n" + out = f"bool {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n" + out += " switch (msg_type) {\n" + for i, case in cases: + c = f"case {i}: {{\n" + c += indent(case) + "\n" + c += "}" + out += indent(c, " ") + "\n" + out += " default:\n" + out += " return false;\n" + out += " }\n" + out += " return true;\n" + out += "}\n" + cpp += out + hpp += "};\n" + + serv = file.service[0] + class_name = "APIServerConnection" + hpp += "\n" + hpp += f"class {class_name} : public {class_name}Base {{\n" + hpp += " public:\n" + hpp_protected = "" + cpp += "\n" + + m = serv.method[0] + for m in serv.method: + func = m.name + inp = m.input_type[1:] + ret = m.output_type[1:] + is_void = ret == "void" + snake = camel_to_snake(inp) + on_func = f"on_{snake}" + needs_conn = get_opt(m, pb.needs_setup_connection, True) + needs_auth = get_opt(m, pb.needs_authentication, True) + + ifdef = ifdefs.get(inp, None) + + if ifdef is not None: + hpp += f"#ifdef {ifdef}\n" + hpp_protected += f"#ifdef {ifdef}\n" + cpp += f"#ifdef {ifdef}\n" + + hpp_protected += f" void {on_func}(const {inp} &msg) override;\n" + hpp += f" virtual {ret} {func}(const {inp} &msg) = 0;\n" + cpp += f"void {class_name}::{on_func}(const {inp} &msg) {{\n" + body = "" + if needs_conn: + body += "if (!this->is_connection_setup()) {\n" + body += " this->on_no_setup_connection();\n" + body += " return;\n" + body += "}\n" + if needs_auth: + body += "if (!this->is_authenticated()) {\n" + body += " this->on_unauthenticated_access();\n" + body += " return;\n" + body += "}\n" + + if is_void: + body += f"this->{func}(msg);\n" + else: + body += f"{ret} ret = this->{func}(msg);\n" + ret_snake = camel_to_snake(ret) + body += f"if (!this->send_{ret_snake}(ret)) {{\n" + body += " this->on_fatal_error();\n" + body += "}\n" + cpp += indent(body) + "\n" + "}\n" + + if ifdef is not None: + hpp += "#endif\n" + hpp_protected += "#endif\n" + cpp += "#endif\n" + + hpp += " protected:\n" + hpp += hpp_protected + hpp += "};\n" + + hpp += """\ + + } // namespace api + } // namespace esphome + """ + cpp += """\ + + } // namespace api + } // namespace esphome + """ + + with open(root / "api_pb2_service.h", "w", encoding="utf-8") as f: + f.write(hpp) + + with open(root / "api_pb2_service.cpp", "w", encoding="utf-8") as f: + f.write(cpp) + + prot_file.unlink() + + try: + import clang_format + + def exec_clang_format(path): + clang_format_path = os.path.join( + os.path.dirname(clang_format.__file__), "data", "bin", "clang-format" + ) + call([clang_format_path, "-i", path]) + + exec_clang_format(root / "api_pb2_service.h") + exec_clang_format(root / "api_pb2_service.cpp") + exec_clang_format(root / "api_pb2.h") + exec_clang_format(root / "api_pb2.cpp") + except ImportError: + pass + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/script/build_language_schema.py b/script/build_language_schema.py index fb2010fe3e..cb3dc1832d 100644 --- a/script/build_language_schema.py +++ b/script/build_language_schema.py @@ -61,6 +61,7 @@ solve_registry = [] def get_component_names(): + # pylint: disable-next=redefined-outer-name,reimported from esphome.loader import CORE_COMPONENTS_PATH component_names = ["esphome", "sensor", "esp32", "esp8266"] @@ -82,9 +83,12 @@ def load_components(): components[domain] = get_component(domain) +# pylint: disable=wrong-import-position from esphome.const import CONF_TYPE, KEY_CORE from esphome.core import CORE +# pylint: enable=wrong-import-position + CORE.data[KEY_CORE] = {} load_components() @@ -114,7 +118,7 @@ def write_file(name, obj): def delete_extra_files(keep_names): for d in os.listdir(args.output_path): - if d.endswith(".json") and not d[:-5] in keep_names: + if d.endswith(".json") and d[:-5] not in keep_names: os.remove(os.path.join(args.output_path, d)) print(f"Deleted {d}") @@ -552,11 +556,11 @@ def shrink(): s = f"{domain}.{schema_name}" if ( not s.endswith("." + S_CONFIG_SCHEMA) - and s not in referenced_schemas.keys() + and s not in referenced_schemas and not is_platform_schema(s) ): print(f"Removing {s}") - output[domain][S_SCHEMAS].pop(schema_name) + domain_schemas[S_SCHEMAS].pop(schema_name) def build_schema(): @@ -564,7 +568,7 @@ def build_schema(): # check esphome was not loaded globally (IDE auto imports) if len(ejs.extended_schemas) == 0: - raise Exception( + raise LookupError( "no data collected. Did you globally import an ESPHome component?" ) @@ -703,7 +707,7 @@ def convert(schema, config_var, path): if schema_instance is schema: assert S_CONFIG_VARS not in config_var assert S_EXTENDS not in config_var - if not S_TYPE in config_var: + if S_TYPE not in config_var: config_var[S_TYPE] = S_SCHEMA # assert config_var[S_TYPE] == S_SCHEMA @@ -765,9 +769,9 @@ def convert(schema, config_var, path): elif schema == automation.validate_potentially_and_condition: config_var[S_TYPE] = "registry" config_var["registry"] = "condition" - elif schema == cv.int_ or schema == cv.int_range: + elif schema in (cv.int_, cv.int_range): config_var[S_TYPE] = "integer" - elif schema == cv.string or schema == cv.string_strict or schema == cv.valid_name: + elif schema in (cv.string, cv.string_strict, cv.valid_name): config_var[S_TYPE] = "string" elif isinstance(schema, vol.Schema): @@ -779,6 +783,7 @@ def convert(schema, config_var, path): config_var |= pin_validators[repr_schema] config_var[S_TYPE] = "pin" + # pylint: disable-next=too-many-nested-blocks elif repr_schema in ejs.hidden_schemas: schema_type = ejs.hidden_schemas[repr_schema] @@ -844,9 +849,11 @@ def convert(schema, config_var, path): config_var["id_type"] = { "class": str(data.base), - "parents": [str(x.base) for x in parents] - if isinstance(parents, list) - else None, + "parents": ( + [str(x.base) for x in parents] + if isinstance(parents, list) + else None + ), } elif schema_type == "use_id": if inspect.ismodule(data): @@ -869,7 +876,7 @@ def convert(schema, config_var, path): config_var["use_id_type"] = str(data.base) config_var[S_TYPE] = "use_id" else: - raise Exception("Unknown extracted schema type") + raise TypeError("Unknown extracted schema type") elif config_var.get("key") == "GeneratedID": if path.startswith("i2c/CONFIG_SCHEMA/") and path.endswith("/id"): config_var["id_type"] = { @@ -884,7 +891,7 @@ def convert(schema, config_var, path): elif path == "pins/esp32/val 1/id": config_var["id_type"] = "pin" else: - raise Exception("Cannot determine id_type for " + path) + raise TypeError("Cannot determine id_type for " + path) elif repr_schema in ejs.registry_schemas: solve_registry.append((ejs.registry_schemas[repr_schema], config_var)) @@ -948,11 +955,7 @@ def convert_keys(converted, schema, path): result["key"] = "GeneratedID" elif isinstance(k, cv.Required): result["key"] = "Required" - elif ( - isinstance(k, cv.Optional) - or isinstance(k, cv.Inclusive) - or isinstance(k, cv.Exclusive) - ): + elif isinstance(k, (cv.Optional, cv.Inclusive, cv.Exclusive)): result["key"] = "Optional" else: converted["key"] = "String" diff --git a/script/bump-version.py b/script/bump-version.py index 3e1e473c4b..a55bb65cd6 100755 --- a/script/bump-version.py +++ b/script/bump-version.py @@ -2,7 +2,6 @@ import argparse import re -import subprocess from dataclasses import dataclass import sys @@ -40,12 +39,12 @@ class Version: def sub(path, pattern, repl, expected_count=1): - with open(path) as fh: + with open(path, encoding="utf-8") as fh: content = fh.read() content, count = re.subn(pattern, repl, content, flags=re.MULTILINE) if expected_count is not None: assert count == expected_count, f"Pattern {pattern} replacement failed!" - with open(path, "w") as fh: + with open(path, "w", encoding="utf-8") as fh: fh.write(content) diff --git a/script/ci-custom.py b/script/ci-custom.py index cc9bdcadbb..1ad44dc930 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 -from helpers import styled, print_error_for_file, git_ls_files, filter_changed import argparse import codecs import collections -import colorama import fnmatch import functools import os.path @@ -12,6 +10,9 @@ import re import sys import time +import colorama +from helpers import filter_changed, git_ls_files, print_error_for_file, styled + sys.path.append(os.path.dirname(__file__)) @@ -30,31 +31,6 @@ def find_all(a_str, sub): column += len(sub) -colorama.init() - -parser = argparse.ArgumentParser() -parser.add_argument( - "files", nargs="*", default=[], help="files to be processed (regex on path)" -) -parser.add_argument( - "-c", "--changed", action="store_true", help="Only run on changed files" -) -parser.add_argument( - "--print-slowest", action="store_true", help="Print the slowest checks" -) -args = parser.parse_args() - -EXECUTABLE_BIT = git_ls_files() -files = list(EXECUTABLE_BIT.keys()) -# Match against re -file_name_re = re.compile("|".join(args.files)) -files = [p for p in files if file_name_re.search(p)] - -if args.changed: - files = filter_changed(files) - -files.sort() - file_types = ( ".h", ".c", @@ -81,11 +57,36 @@ file_types = ( "", ) cpp_include = ("*.h", "*.c", "*.cpp", "*.tcc") +py_include = ("*.py",) ignore_types = (".ico", ".png", ".woff", ".woff2", "") LINT_FILE_CHECKS = [] LINT_CONTENT_CHECKS = [] LINT_POST_CHECKS = [] +EXECUTABLE_BIT = {} + +errors = collections.defaultdict(list) + + +def add_errors(fname, errs): + if not isinstance(errs, list): + errs = [errs] + for err in errs: + if err is None: + continue + try: + lineno, col, msg = err + except ValueError: + lineno = 1 + col = 1 + msg = err + if not isinstance(msg, str): + raise ValueError("Error is not instance of string!") + if not isinstance(lineno, int): + raise ValueError("Line number is not an int!") + if not isinstance(col, int): + raise ValueError("Column number is not an int!") + errors[fname].append((lineno, col, msg)) def run_check(lint_obj, fname, *args): @@ -155,7 +156,7 @@ def lint_re_check(regex, **kwargs): def decorator(func): @functools.wraps(func) def new_func(fname, content): - errors = [] + errs = [] for match in prog.finditer(content): if "NOLINT" in match.group(0): continue @@ -165,8 +166,8 @@ def lint_re_check(regex, **kwargs): err = func(fname, match) if err is None: continue - errors.append((lineno, col + 1, err)) - return errors + errs.append((lineno, col + 1, err)) + return errs return decor(new_func) @@ -182,13 +183,13 @@ def lint_content_find_check(find, only_first=False, **kwargs): find_ = find if callable(find): find_ = find(fname, content) - errors = [] + errs = [] for line, col in find_all(content, find_): err = func(fname) - errors.append((line + 1, col + 1, err)) + errs.append((line + 1, col + 1, err)) if only_first: break - return errors + return errs return decor(new_func) @@ -235,8 +236,8 @@ def lint_executable_bit(fname): ex = EXECUTABLE_BIT[fname] if ex != 100644: return ( - "File has invalid executable bit {}. If running from a windows machine please " - "see disabling executable bit in git.".format(ex) + f"File has invalid executable bit {ex}. If running from a windows machine please " + "see disabling executable bit in git." ) return None @@ -265,7 +266,8 @@ def lint_end_newline(fname, content): return None -CPP_RE_EOL = r"\s*?(?://.*?)?$" +CPP_RE_EOL = r".*?(?://.*?)?$" +PY_RE_EOL = r".*?(?:#.*?)?$" def highlight(s): @@ -273,7 +275,7 @@ def highlight(s): @lint_re_check( - r"^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)" + CPP_RE_EOL, + r"^#define\s+([a-zA-Z0-9_]+)\s+(0b[10]+|0x[0-9a-fA-F]+|\d+)\s*?(?:\/\/.*?)?$", include=cpp_include, exclude=[ "esphome/core/log.h", @@ -285,8 +287,8 @@ def lint_no_defines(fname, match): s = highlight(f"static const uint8_t {match.group(1)} = {match.group(2)};") return ( "#define macros for integer constants are not allowed, please use " - "{} style instead (replace uint8_t with the appropriate " - "datatype). See also Google style guide.".format(s) + f"{s} style instead (replace uint8_t with the appropriate " + "datatype). See also Google style guide." ) @@ -296,11 +298,11 @@ def lint_no_long_delays(fname, match): if duration_ms < 50: return None return ( - "{} - long calls to delay() are not allowed in ESPHome because everything executes " - "in one thread. Calling delay() will block the main thread and slow down ESPHome.\n" + f"{highlight(match.group(0).strip())} - long calls to delay() are not allowed " + "in ESPHome because everything executes in one thread. Calling delay() will " + "block the main thread and slow down ESPHome.\n" "If there's no way to work around the delay() and it doesn't execute often, please add " "a '// NOLINT' comment to the line." - "".format(highlight(match.group(0).strip())) ) @@ -311,28 +313,28 @@ def lint_const_ordered(fname, content): Reason: Otherwise people add it to the end, and then that results in merge conflicts. """ lines = content.splitlines() - errors = [] + errs = [] for start in ["CONF_", "ICON_", "UNIT_"]: matching = [ (i + 1, line) for i, line in enumerate(lines) if line.startswith(start) ] ordered = list(sorted(matching, key=lambda x: x[1].replace("_", " "))) ordered = [(mi, ol) for (mi, _), (_, ol) in zip(matching, ordered)] - for (mi, ml), (oi, ol) in zip(matching, ordered): - if ml == ol: + for (mi, mline), (_, ol) in zip(matching, ordered): + if mline == ol: continue - target = next(i for i, l in ordered if l == ml) - target_text = next(l for i, l in matching if target == i) - errors.append( + target = next(i for i, line in ordered if line == mline) + target_text = next(line for i, line in matching if target == i) + errs.append( ( mi, 1, - f"Constant {highlight(ml)} is not ordered, please make sure all " + f"Constant {highlight(mline)} is not ordered, please make sure all " f"constants are ordered. See line {mi} (should go to line {target}, " f"{target_text})", ) ) - return errors + return errs @lint_re_check(r'^\s*CONF_([A-Z_0-9a-z]+)\s+=\s+[\'"](.*?)[\'"]\s*?$', include=["*.py"]) @@ -344,15 +346,14 @@ def lint_conf_matches(fname, match): if const_norm == value_norm: return None return ( - "Constant {} does not match value {}! Please make sure the constant's name matches its " - "value!" - "".format(highlight("CONF_" + const), highlight(value)) + f"Constant {highlight('CONF_' + const)} does not match value {highlight(value)}! " + "Please make sure the constant's name matches its value!" ) CONF_RE = r'^(CONF_[a-zA-Z0-9_]+)\s*=\s*[\'"].*?[\'"]\s*?$' -with codecs.open("esphome/const.py", "r", encoding="utf-8") as f_handle: - constants_content = f_handle.read() +with codecs.open("esphome/const.py", "r", encoding="utf-8") as const_f_handle: + constants_content = const_f_handle.read() CONSTANTS = [m.group(1) for m in re.finditer(CONF_RE, constants_content, re.MULTILINE)] CONSTANTS_USES = collections.defaultdict(list) @@ -365,8 +366,8 @@ def lint_conf_from_const_py(fname, match): CONSTANTS_USES[name].append(fname) return None return ( - "Constant {} has already been defined in const.py - please import the constant from " - "const.py directly.".format(highlight(name)) + f"Constant {highlight(name)} has already been defined in const.py - " + "please import the constant from const.py directly." ) @@ -473,16 +474,15 @@ def lint_no_byte_datatype(fname, match): @lint_post_check def lint_constants_usage(): - errors = [] + errs = [] for constant, uses in CONSTANTS_USES.items(): if len(uses) < 4: continue - errors.append( - "Constant {} is defined in {} files. Please move all definitions of the " - "constant to const.py (Uses: {})" - "".format(highlight(constant), len(uses), ", ".join(uses)) + errs.append( + f"Constant {highlight(constant)} is defined in {len(uses)} files. Please move all definitions of the " + f"constant to const.py (Uses: {', '.join(uses)})" ) - return errors + return errs def relative_cpp_search_text(fname, content): @@ -553,7 +553,7 @@ def lint_namespace(fname, content): return ( "Invalid namespace found in C++ file. All integration C++ files should put all " "functions in a separate namespace that matches the integration's name. " - "Please make sure the file contains {}".format(highlight(search)) + f"Please make sure the file contains {highlight(search)}" ) @@ -576,11 +576,6 @@ def lint_pragma_once(fname, content): return None -@lint_re_check( - r"(whitelist|blacklist|slave)", - exclude=["script/ci-custom.py"], - flags=re.IGNORECASE | re.MULTILINE, -) def lint_inclusive_language(fname, match): # From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=49decddd39e5f6132ccd7d9fdc3d7c470b0061bb return ( @@ -598,6 +593,21 @@ def lint_inclusive_language(fname, match): ) +lint_re_check( + r"(whitelist|blacklist|slave)" + PY_RE_EOL, + include=py_include, + exclude=["script/ci-custom.py"], + flags=re.IGNORECASE | re.MULTILINE, +)(lint_inclusive_language) + + +lint_re_check( + r"(whitelist|blacklist|slave)" + CPP_RE_EOL, + include=cpp_include, + flags=re.IGNORECASE | re.MULTILINE, +)(lint_inclusive_language) + + @lint_re_check(r"[\t\r\f\v ]+$") def lint_trailing_whitespace(fname, match): return "Trailing whitespace detected" @@ -611,6 +621,7 @@ def lint_trailing_whitespace(fname, match): "esphome/components/button/button.h", "esphome/components/climate/climate.h", "esphome/components/cover/cover.h", + "esphome/components/datetime/date_entity.h", "esphome/components/display/display.h", "esphome/components/fan/fan.h", "esphome/components/i2c/i2c.h", @@ -639,66 +650,73 @@ def lint_log_in_header(fname): ) -errors = collections.defaultdict(list) +def main(): + colorama.init() + parser = argparse.ArgumentParser() + parser.add_argument( + "files", nargs="*", default=[], help="files to be processed (regex on path)" + ) + parser.add_argument( + "-c", "--changed", action="store_true", help="Only run on changed files" + ) + parser.add_argument( + "--print-slowest", action="store_true", help="Print the slowest checks" + ) + args = parser.parse_args() -def add_errors(fname, errs): - if not isinstance(errs, list): - errs = [errs] - for err in errs: - if err is None: + global EXECUTABLE_BIT + EXECUTABLE_BIT = git_ls_files() + files = list(EXECUTABLE_BIT.keys()) + # Match against re + file_name_re = re.compile("|".join(args.files)) + files = [p for p in files if file_name_re.search(p)] + + if args.changed: + files = filter_changed(files) + + files.sort() + + for fname in files: + _, ext = os.path.splitext(fname) + run_checks(LINT_FILE_CHECKS, fname, fname) + if ext in ignore_types: continue try: - lineno, col, msg = err - except ValueError: - lineno = 1 - col = 1 - msg = err - if not isinstance(msg, str): - raise ValueError("Error is not instance of string!") - if not isinstance(lineno, int): - raise ValueError("Line number is not an int!") - if not isinstance(col, int): - raise ValueError("Column number is not an int!") - errors[fname].append((lineno, col, msg)) + with codecs.open(fname, "r", encoding="utf-8") as f_handle: + content = f_handle.read() + except UnicodeDecodeError: + add_errors( + fname, + "File is not readable as UTF-8. Please set your editor to UTF-8 mode.", + ) + continue + run_checks(LINT_CONTENT_CHECKS, fname, fname, content) + run_checks(LINT_POST_CHECKS, "POST") -for fname in files: - _, ext = os.path.splitext(fname) - run_checks(LINT_FILE_CHECKS, fname, fname) - if ext in ignore_types: - continue - try: - with codecs.open(fname, "r", encoding="utf-8") as f_handle: - content = f_handle.read() - except UnicodeDecodeError: - add_errors( - fname, - "File is not readable as UTF-8. Please set your editor to UTF-8 mode.", + for f, errs in sorted(errors.items()): + bold = functools.partial(styled, colorama.Style.BRIGHT) + bold_red = functools.partial(styled, (colorama.Style.BRIGHT, colorama.Fore.RED)) + err_str = ( + f"{bold(f'{f}:{lineno}:{col}:')} {bold_red('lint:')} {msg}\n" + for lineno, col, msg in errs ) - continue - run_checks(LINT_CONTENT_CHECKS, fname, fname, content) + print_error_for_file(f, "\n".join(err_str)) -run_checks(LINT_POST_CHECKS, "POST") + if args.print_slowest: + lint_times = [] + for lint in LINT_FILE_CHECKS + LINT_CONTENT_CHECKS + LINT_POST_CHECKS: + durations = lint.get("durations", []) + lint_times.append((sum(durations), len(durations), lint["func"].__name__)) + lint_times.sort(key=lambda x: -x[0]) + for i in range(min(len(lint_times), 10)): + dur, invocations, name = lint_times[i] + print(f" - '{name}' took {dur:.2f}s total (ran on {invocations} files)") + print(f"Total time measured: {sum(x[0] for x in lint_times):.2f}s") -for f, errs in sorted(errors.items()): - bold = functools.partial(styled, colorama.Style.BRIGHT) - bold_red = functools.partial(styled, (colorama.Style.BRIGHT, colorama.Fore.RED)) - err_str = ( - f"{bold(f'{f}:{lineno}:{col}:')} {bold_red('lint:')} {msg}\n" - for lineno, col, msg in errs - ) - print_error_for_file(f, "\n".join(err_str)) + return len(errors) -if args.print_slowest: - lint_times = [] - for lint in LINT_FILE_CHECKS + LINT_CONTENT_CHECKS + LINT_POST_CHECKS: - durations = lint.get("durations", []) - lint_times.append((sum(durations), len(durations), lint["func"].__name__)) - lint_times.sort(key=lambda x: -x[0]) - for i in range(min(len(lint_times), 10)): - dur, invocations, name = lint_times[i] - print(f" - '{name}' took {dur:.2f}s total (ran on {invocations} files)") - print(f"Total time measured: {sum(x[0] for x in lint_times):.2f}s") -sys.exit(len(errors)) +if __name__ == "__main__": + sys.exit(main()) diff --git a/script/clang-format b/script/clang-format index 165fbd269f..b065d80795 100755 --- a/script/clang-format +++ b/script/clang-format @@ -1,6 +1,12 @@ #!/usr/bin/env python3 -from helpers import print_error_for_file, get_output, git_ls_files, filter_changed +from helpers import ( + print_error_for_file, + get_output, + git_ls_files, + filter_changed, + get_binary, +) import argparse import click import colorama @@ -13,11 +19,12 @@ import sys import threading -def run_format(args, queue, lock, failed_files): + +def run_format(executable, args, queue, lock, failed_files): """Takes filenames out of queue and runs clang-format on them.""" while True: path = queue.get() - invocation = ["clang-format-13"] + invocation = [executable] if args.inplace: invocation.append("-i") else: @@ -58,22 +65,6 @@ def main(): ) args = parser.parse_args() - try: - get_output("clang-format-13", "-version") - except: - print( - """ - Oops. It looks like clang-format is not installed. - - Please check you can run "clang-format-13 -version" in your terminal and install - clang-format (v13) if necessary. - - Note you can also upload your code as a pull request on GitHub and see the CI check - output to apply clang-format. - """ - ) - return 1 - files = [] for path in git_ls_files(["*.cpp", "*.h", "*.tcc"]): files.append(os.path.relpath(path, os.getcwd())) @@ -90,11 +81,12 @@ def main(): failed_files = [] try: + executable = get_binary("clang-format", 13) task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): t = threading.Thread( - target=run_format, args=(args, task_queue, lock, failed_files) + target=run_format, args=(executable, args, task_queue, lock, failed_files) ) t.daemon = True t.start() @@ -109,13 +101,18 @@ def main(): # Wait for all threads to be done. task_queue.join() + except FileNotFoundError as ex: + return 1 except KeyboardInterrupt: print() print("Ctrl-C detected, goodbye.") + # Kill subprocesses (and ourselves!) + # No simple, clean alternative appears to be available. os.kill(0, 9) + return 2 # Will not execute. - sys.exit(len(failed_files)) + return len(failed_files) if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/script/clang-tidy b/script/clang-tidy index b025221fa8..97e4ba0d48 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -11,6 +11,7 @@ from helpers import ( load_idedata, root_path, basepath, + get_binary, ) import argparse import click @@ -26,6 +27,7 @@ import tempfile import threading + def clang_options(idedata): cmd = [] @@ -110,10 +112,12 @@ def clang_options(idedata): return cmd -def run_tidy(args, options, tmpdir, queue, lock, failed_files): +pids = set() + +def run_tidy(executable, args, options, tmpdir, queue, lock, failed_files): while True: path = queue.get() - invocation = ["clang-tidy-14"] + invocation = [executable] if tmpdir is not None: invocation.append("--export-fixes") @@ -193,22 +197,6 @@ def main(): ) args = parser.parse_args() - try: - get_output("clang-tidy-14", "-version") - except: - print( - """ - Oops. It looks like clang-tidy-14 is not installed. - - Please check you can run "clang-tidy-14 -version" in your terminal and install - clang-tidy (v14) if necessary. - - Note you can also upload your code as a pull request on GitHub and see the CI check - output to apply clang-tidy. - """ - ) - return 1 - idedata = load_idedata(args.environment) options = clang_options(idedata) @@ -242,12 +230,13 @@ def main(): failed_files = [] try: + executable = get_binary("clang-tidy", 14) task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): t = threading.Thread( target=run_tidy, - args=(args, options, tmpdir, task_queue, lock, failed_files), + args=(executable, args, options, tmpdir, task_queue, lock, failed_files), ) t.daemon = True t.start() @@ -262,12 +251,17 @@ def main(): # Wait for all threads to be done. task_queue.join() + except FileNotFoundError as ex: + return 1 except KeyboardInterrupt: print() print("Ctrl-C detected, goodbye.") if tmpdir: shutil.rmtree(tmpdir) + # Kill subprocesses (and ourselves!) + # No simple, clean alternative appears to be available. os.kill(0, 9) + return 2 # Will not execute. if args.fix and failed_files: print("Applying fixes ...") @@ -277,8 +271,8 @@ def main(): print("Error applying fixes.\n", file=sys.stderr) raise - sys.exit(len(failed_files)) + return len(failed_files) if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/script/devcontainer-post-create b/script/devcontainer-post-create index 120ab3307d..272d350519 100755 --- a/script/devcontainer-post-create +++ b/script/devcontainer-post-create @@ -3,6 +3,9 @@ set -e # set -x +apt update +apt-get install avahi-utils -y + mkdir -p config script/setup diff --git a/script/fulltest b/script/fulltest index a605beebfe..6440401e97 100755 --- a/script/fulltest +++ b/script/fulltest @@ -12,3 +12,4 @@ script/lint-cpp script/unit_test script/component_test script/test +script/test_build_components diff --git a/script/helpers.py b/script/helpers.py index c042362aeb..52b0658fb6 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -1,10 +1,11 @@ -import colorama +import json import os.path import re import subprocess -import json from pathlib import Path +import colorama + root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, "..", ".."))) basepath = os.path.join(root_path, "esphome") temp_folder = os.path.join(root_path, ".temp") @@ -44,7 +45,7 @@ def build_all_include(): content = "\n".join(headers) p = Path(temp_header_file) p.parent.mkdir(exist_ok=True) - p.write_text(content) + p.write_text(content, encoding="utf-8") def walk_files(path): @@ -54,14 +55,14 @@ def walk_files(path): def get_output(*args): - proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - output, err = proc.communicate() + with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc: + output, _ = proc.communicate() return output.decode("utf-8") def get_err(*args): - proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - output, err = proc.communicate() + with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc: + _, err = proc.communicate() return err.decode("utf-8") @@ -69,16 +70,16 @@ def splitlines_no_ends(string): return [s.strip() for s in string.splitlines()] -def changed_files(): +def changed_files(branch="dev"): check_remotes = ["upstream", "origin"] check_remotes.extend(splitlines_no_ends(get_output("git", "remote"))) for remote in check_remotes: - command = ["git", "merge-base", f"refs/remotes/{remote}/dev", "HEAD"] + command = ["git", "merge-base", f"refs/remotes/{remote}/{branch}", "HEAD"] try: merge_base = splitlines_no_ends(get_output(*command))[0] break # pylint: disable=bare-except - except: + except: # noqa: E722 pass else: raise ValueError("Git not configured") @@ -103,7 +104,7 @@ def filter_changed(files): def filter_grep(files, value): matched = [] for file in files: - with open(file) as handle: + with open(file, encoding="utf-8") as handle: contents = handle.read() if value in contents: matched.append(file) @@ -114,8 +115,8 @@ def git_ls_files(patterns=None): command = ["git", "ls-files", "-s"] if patterns is not None: command.extend(patterns) - proc = subprocess.Popen(command, stdout=subprocess.PIPE) - output, err = proc.communicate() + with subprocess.Popen(command, stdout=subprocess.PIPE) as proc: + output, _ = proc.communicate() lines = [x.split() for x in output.decode("utf-8").splitlines()] return {s[3].strip(): int(s[0]) for s in lines} @@ -152,3 +153,39 @@ def load_idedata(environment): temp_idedata.write_text(json.dumps(data, indent=2) + "\n") return data + + +def get_binary(name: str, version: str) -> str: + binary_file = f"{name}-{version}" + try: + result = subprocess.check_output([binary_file, "-version"]) + if result.returncode == 0: + return binary_file + except Exception: + pass + binary_file = name + try: + result = subprocess.run( + [binary_file, "-version"], text=True, capture_output=True + ) + if result.returncode == 0 and (f"version {version}") in result.stdout: + return binary_file + raise FileNotFoundError(f"{name} not found") + + except FileNotFoundError as ex: + print( + f""" + Oops. It looks like {name} is not installed. It should be available under venv/bin + and in PATH after running in turn: + script/setup + source venv/bin/activate. + + Please confirm you can run "{name} -version" or "{name}-{version} -version" + in your terminal and install + {name} (v{version}) if necessary. + + Note you can also upload your code as a pull request on GitHub and see the CI check + output to apply {name} + """ + ) + raise diff --git a/script/list-components.py b/script/list-components.py new file mode 100755 index 0000000000..8e2d47c6b3 --- /dev/null +++ b/script/list-components.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +from pathlib import Path +import sys +import argparse + +from helpers import git_ls_files, changed_files +from esphome.loader import get_component, get_platform +from esphome.core import CORE +from esphome.const import ( + KEY_CORE, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, + PLATFORM_ESP32, + PLATFORM_ESP8266, +) + + +def filter_component_files(str): + return str.startswith("esphome/components/") | str.startswith("tests/components/") + + +def extract_component_names_array_from_files_array(files): + components = [] + for file in files: + file_parts = file.split("/") + if len(file_parts) >= 4: + component_name = file_parts[2] + if component_name not in components: + components.append(component_name) + return components + + +def add_item_to_components_graph(components_graph, parent, child): + if not parent.startswith("__") and parent != child: + if parent not in components_graph: + components_graph[parent] = [] + if child not in components_graph[parent]: + components_graph[parent].append(child) + + +def create_components_graph(): + # The root directory of the repo + root = Path(__file__).parent.parent + components_dir = root / "esphome" / "components" + # Fake some directory so that get_component works + CORE.config_path = str(root) + # Various configuration to capture different outcomes used by `AUTO_LOAD` function. + TARGET_CONFIGURATIONS = [ + {KEY_TARGET_FRAMEWORK: None, KEY_TARGET_PLATFORM: None}, + {KEY_TARGET_FRAMEWORK: "arduino", KEY_TARGET_PLATFORM: None}, + {KEY_TARGET_FRAMEWORK: "esp-idf", KEY_TARGET_PLATFORM: None}, + {KEY_TARGET_FRAMEWORK: None, KEY_TARGET_PLATFORM: PLATFORM_ESP32}, + ] + CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0] + + components_graph = {} + + for path in components_dir.iterdir(): + if not path.is_dir(): + continue + if not (path / "__init__.py").is_file(): + continue + name = path.name + comp = get_component(name) + if comp is None: + print( + f"Cannot find component {name}. Make sure current path is pip installed ESPHome" + ) + sys.exit(1) + + for dependency in comp.dependencies: + add_item_to_components_graph(components_graph, dependency, name) + + for target_config in TARGET_CONFIGURATIONS: + CORE.data[KEY_CORE] = target_config + for auto_load in comp.auto_load: + add_item_to_components_graph(components_graph, auto_load, name) + # restore config + CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0] + + for platform_path in path.iterdir(): + platform_name = platform_path.stem + platform = get_platform(platform_name, name) + if platform is None: + continue + + add_item_to_components_graph(components_graph, platform_name, name) + + for dependency in platform.dependencies: + add_item_to_components_graph(components_graph, dependency, name) + + for target_config in TARGET_CONFIGURATIONS: + CORE.data[KEY_CORE] = target_config + for auto_load in platform.auto_load: + add_item_to_components_graph(components_graph, auto_load, name) + # restore config + CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0] + + return components_graph + + +def find_children_of_component(components_graph, component_name, depth=0): + if component_name not in components_graph: + return [] + + children = [] + + for child in components_graph[component_name]: + children.append(child) + if depth < 10: + children.extend( + find_children_of_component(components_graph, child, depth + 1) + ) + # Remove duplicate values + return list(set(children)) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-c", "--changed", action="store_true", help="Only run on changed files" + ) + parser.add_argument( + "-b", "--branch", help="Branch to compare changed files against" + ) + args = parser.parse_args() + + if args.branch and not args.changed: + parser.error("--branch requires --changed") + + files = git_ls_files() + files = filter(filter_component_files, files) + + if args.changed: + if args.branch: + changed = changed_files(args.branch) + else: + changed = changed_files() + files = [f for f in files if f in changed] + + components = extract_component_names_array_from_files_array(files) + + if args.changed: + components_graph = create_components_graph() + + all_changed_components = components.copy() + for c in components: + all_changed_components.extend( + find_children_of_component(components_graph, c) + ) + # Remove duplicate values + all_changed_components = list(set(all_changed_components)) + + for c in sorted(all_changed_components): + print(c) + else: + for c in sorted(components): + print(c) + + +if __name__ == "__main__": + main() diff --git a/script/setup b/script/setup index ba3b544352..f286b4672a 100755 --- a/script/setup +++ b/script/setup @@ -4,10 +4,13 @@ set -e cd "$(dirname "$0")/.." - +location="venv/bin/activate" if [ ! -n "$DEVCONTAINER" ] && [ ! -n "$VIRTUAL_ENV" ] && [ ! "$ESPHOME_NO_VENV" ]; then python3 -m venv venv - source venv/bin/activate + if [ -f venv/Scripts/activate ]; then + location="venv/Scripts/activate" + fi + source $location; fi # Avoid unsafe git error when running inside devcontainer @@ -15,10 +18,14 @@ if [ -n "$DEVCONTAINER" ];then git config --global --add safe.directory "$PWD" fi -pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt +pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt -r requirements_dev.txt pip3 install setuptools wheel pip3 install --no-use-pep517 -e . pre-commit install script/platformio_install_deps.py platformio.ini --libraries --tools --platforms + +echo +echo +echo "Virtual environment created. Run 'source $location' to use it." diff --git a/script/sync-device_class.py b/script/sync-device_class.py index ae6f4be0c8..8f91b97997 100755 --- a/script/sync-device_class.py +++ b/script/sync-device_class.py @@ -2,6 +2,7 @@ import re +# pylint: disable=import-error from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.button import ButtonDeviceClass from homeassistant.components.cover import CoverDeviceClass @@ -9,6 +10,8 @@ from homeassistant.components.number import NumberDeviceClass from homeassistant.components.sensor import SensorDeviceClass from homeassistant.components.switch import SwitchDeviceClass +# pylint: enable=import-error + BLOCKLIST = ( # requires special support on HA side "enum", @@ -25,10 +28,10 @@ DOMAINS = { def sub(path, pattern, repl): - with open(path) as handle: + with open(path, encoding="utf-8") as handle: content = handle.read() content = re.sub(pattern, repl, content, flags=re.MULTILINE) - with open(path, "w") as handle: + with open(path, "w", encoding="utf-8") as handle: handle.write(content) diff --git a/script/test_build_components b/script/test_build_components new file mode 100755 index 0000000000..2396353198 --- /dev/null +++ b/script/test_build_components @@ -0,0 +1,90 @@ +#!/usr/bin/env bash + +set -e + +# Parse parameter: +# - `e` - Parameter for `esphome` command. Default `compile`. Common alternative is `config`. +# - `c` - Component folder name to test. Default `*`. +esphome_command="compile" +target_component="*" +while getopts e:c: flag +do + case $flag in + e) esphome_command=${OPTARG};; + c) target_component=${OPTARG};; + \?) echo "Usage: $0 [-e ] [-c ]" 1>&2; exit 1;; + esac +done + +cd "$(dirname "$0")/.." + +if ! [ -d "./tests/test_build_components/build" ]; then + mkdir ./tests/test_build_components/build +fi + +start_esphome() { + # create dynamic yaml file in `build` folder. + # `./tests/test_build_components/build/[target_component].[test_name].[target_platform].yaml` + component_test_file="./tests/test_build_components/build/$target_component.$test_name.$target_platform.yaml" + + cp $target_platform_file $component_test_file + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS sed is...different + sed -i '' "s!\$component_test_file!../../.$f!g" $component_test_file + else + sed -i "s!\$component_test_file!../../.$f!g" $component_test_file + fi + + # Start esphome process + echo "> [$target_component] [$test_name] [$target_platform]" + echo "esphome -s component_name $target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file" + # TODO: Validate escape of Command line substitution value + esphome -s component_name $target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file +} + +# Find all test yaml files. +# - `./tests/components/[target_component]/[test_name].[target_platform].yaml` +# - `./tests/components/[target_component]/[test_name].all.yaml` +for f in ./tests/components/$target_component/*.*.yaml; do + [ -f "$f" ] || continue + IFS='/' read -r -a folder_name <<< "$f" + target_component="${folder_name[3]}" + + IFS='.' read -r -a file_name <<< "${folder_name[4]}" + test_name="${file_name[0]}" + target_platform="${file_name[1]}" + file_name_parts=${#file_name[@]} + + if [ "$target_platform" = "all" ] || [ $file_name_parts = 2 ]; then + # Test has *not* defined a specific target platform. Need to run tests for all possible target platforms. + + for target_platform_file in ./tests/test_build_components/build_components_base.*.yaml; do + IFS='/' read -r -a folder_name <<< "$target_platform_file" + IFS='.' read -r -a file_name <<< "${folder_name[3]}" + target_platform="${file_name[1]}" + + start_esphome + done + + else + # Test has defined a specific target platform. + + # Validate we have a base test yaml for selected platform. + # The target_platform is sourced from the following location. + # 1. `./tests/test_build_components/build_components_base.[target_platform].yaml` + # 2. `./tests/test_build_components/build_components_base.[target_platform]-ard.yaml` + target_platform_file="./tests/test_build_components/build_components_base.$target_platform.yaml" + if ! [ -f "$target_platform_file" ]; then + # Try find arduino test framework as platform. + target_platform_ard="$target_platform-ard" + target_platform_file="./tests/test_build_components/build_components_base.$target_platform_ard.yaml" + if ! [ -f "$target_platform_file" ]; then + echo "No base test file [./tests/test_build_components/build_components_base.$target_platform.yaml, ./tests/build_components_base.$target_platform_ard.yaml] for component test [$f] found." + exit 1 + fi + target_platform=$target_platform_ard + fi + + start_esphome + fi +done diff --git a/setup.cfg b/setup.cfg index 755cef47c0..b3cfbba6a1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,7 +13,6 @@ classifier = Programming Language :: C++ Programming Language :: Python :: 3 Topic :: Home Automation -Topic :: Home Automation [flake8] max-line-length = 120 diff --git a/tests/component_tests/text_sensor/test_text_sensor.py b/tests/component_tests/text_sensor/test_text_sensor.py new file mode 100644 index 0000000000..1c4ef6633d --- /dev/null +++ b/tests/component_tests/text_sensor/test_text_sensor.py @@ -0,0 +1,58 @@ +"""Tests for the text sensor component.""" + + +def test_text_sensor_is_setup(generate_main): + """ + When the text is set in the yaml file, it should be registered in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") + + # Then + assert "new template_::TemplateTextSensor();" in main_cpp + assert "App.register_text_sensor" in main_cpp + + +def test_text_sensor_sets_mandatory_fields(generate_main): + """ + When the mandatory fields are set in the yaml, they should be set in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") + + # Then + assert 'ts_1->set_name("Template Text Sensor 1");' in main_cpp + assert 'ts_2->set_name("Template Text Sensor 2");' in main_cpp + assert 'ts_3->set_name("Template Text Sensor 3");' in main_cpp + + +def test_text_sensor_config_value_internal_set(generate_main): + """ + Test that the "internal" config value is correctly set + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") + + # Then + assert "ts_2->set_internal(true);" in main_cpp + assert "ts_3->set_internal(false);" in main_cpp + + +def test_text_sensor_device_class_set(generate_main): + """ + When the device_class of text_sensor is set in the yaml file, it should be registered in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") + + # Then + assert 'ts_2->set_device_class("timestamp");' in main_cpp + assert 'ts_3->set_device_class("date");' in main_cpp diff --git a/tests/component_tests/text_sensor/test_text_sensor.yaml b/tests/component_tests/text_sensor/test_text_sensor.yaml new file mode 100644 index 0000000000..b426cb102c --- /dev/null +++ b/tests/component_tests/text_sensor/test_text_sensor.yaml @@ -0,0 +1,26 @@ +--- +esphome: + name: test + platform: ESP8266 + board: d1_mini_lite + +text_sensor: + - platform: template + id: ts_1 + name: "Template Text Sensor 1" + lambda: |- + return {"Hello World"}; + - platform: template + id: ts_2 + name: "Template Text Sensor 2" + lambda: |- + return {"2023-06-22T18:43:52+00:00"}; + device_class: timestamp + internal: true + - platform: template + id: ts_3 + name: "Template Text Sensor 3" + lambda: |- + return {"2023-06-22T18:43:52+00:00"}; + device_class: date + internal: false diff --git a/tests/components/a01nyub/test.esp32-c3-idf.yaml b/tests/components/a01nyub/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..3132f77136 --- /dev/null +++ b/tests/components/a01nyub/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a01nyub + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a01nyub + id: a01nyub_sensor + name: a01nyub Distance + uart_id: uart_a01nyub diff --git a/tests/components/a01nyub/test.esp32-c3.yaml b/tests/components/a01nyub/test.esp32-c3.yaml new file mode 100644 index 0000000000..3132f77136 --- /dev/null +++ b/tests/components/a01nyub/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a01nyub + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a01nyub + id: a01nyub_sensor + name: a01nyub Distance + uart_id: uart_a01nyub diff --git a/tests/components/a01nyub/test.esp32-idf.yaml b/tests/components/a01nyub/test.esp32-idf.yaml new file mode 100644 index 0000000000..79fc9c5fbf --- /dev/null +++ b/tests/components/a01nyub/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a01nyub + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: a01nyub + id: a01nyub_sensor + name: a01nyub Distance + uart_id: uart_a01nyub diff --git a/tests/components/a01nyub/test.esp32.yaml b/tests/components/a01nyub/test.esp32.yaml new file mode 100644 index 0000000000..79fc9c5fbf --- /dev/null +++ b/tests/components/a01nyub/test.esp32.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a01nyub + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: a01nyub + id: a01nyub_sensor + name: a01nyub Distance + uart_id: uart_a01nyub diff --git a/tests/components/a01nyub/test.esp8266.yaml b/tests/components/a01nyub/test.esp8266.yaml new file mode 100644 index 0000000000..3132f77136 --- /dev/null +++ b/tests/components/a01nyub/test.esp8266.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a01nyub + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a01nyub + id: a01nyub_sensor + name: a01nyub Distance + uart_id: uart_a01nyub diff --git a/tests/components/a01nyub/test.rp2040.yaml b/tests/components/a01nyub/test.rp2040.yaml new file mode 100644 index 0000000000..3132f77136 --- /dev/null +++ b/tests/components/a01nyub/test.rp2040.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a01nyub + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a01nyub + id: a01nyub_sensor + name: a01nyub Distance + uart_id: uart_a01nyub diff --git a/tests/components/a02yyuw/test.esp32-c3-idf.yaml b/tests/components/a02yyuw/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..76e1ad8ee1 --- /dev/null +++ b/tests/components/a02yyuw/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a02yyuw + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a02yyuw + id: a02yyuw_sensor + name: a02yyuw Distance + uart_id: uart_a02yyuw diff --git a/tests/components/a02yyuw/test.esp32-c3.yaml b/tests/components/a02yyuw/test.esp32-c3.yaml new file mode 100644 index 0000000000..76e1ad8ee1 --- /dev/null +++ b/tests/components/a02yyuw/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a02yyuw + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a02yyuw + id: a02yyuw_sensor + name: a02yyuw Distance + uart_id: uart_a02yyuw diff --git a/tests/components/a02yyuw/test.esp32-idf.yaml b/tests/components/a02yyuw/test.esp32-idf.yaml new file mode 100644 index 0000000000..98d6a266b3 --- /dev/null +++ b/tests/components/a02yyuw/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a02yyuw + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: a02yyuw + id: a02yyuw_sensor + name: a02yyuw Distance + uart_id: uart_a02yyuw diff --git a/tests/components/a02yyuw/test.esp32.yaml b/tests/components/a02yyuw/test.esp32.yaml new file mode 100644 index 0000000000..98d6a266b3 --- /dev/null +++ b/tests/components/a02yyuw/test.esp32.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a02yyuw + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: a02yyuw + id: a02yyuw_sensor + name: a02yyuw Distance + uart_id: uart_a02yyuw diff --git a/tests/components/a02yyuw/test.esp8266.yaml b/tests/components/a02yyuw/test.esp8266.yaml new file mode 100644 index 0000000000..76e1ad8ee1 --- /dev/null +++ b/tests/components/a02yyuw/test.esp8266.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a02yyuw + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a02yyuw + id: a02yyuw_sensor + name: a02yyuw Distance + uart_id: uart_a02yyuw diff --git a/tests/components/a02yyuw/test.rp2040.yaml b/tests/components/a02yyuw/test.rp2040.yaml new file mode 100644 index 0000000000..76e1ad8ee1 --- /dev/null +++ b/tests/components/a02yyuw/test.rp2040.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a02yyuw + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a02yyuw + id: a02yyuw_sensor + name: a02yyuw Distance + uart_id: uart_a02yyuw diff --git a/tests/components/a4988/test.esp32-c3-idf.yaml b/tests/components/a4988/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..af4e4fa32b --- /dev/null +++ b/tests/components/a4988/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +stepper: + - platform: a4988 + id: a4988_stepper + step_pin: + number: 2 + dir_pin: + number: 3 + sleep_pin: + number: 5 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/a4988/test.esp32-c3.yaml b/tests/components/a4988/test.esp32-c3.yaml new file mode 100644 index 0000000000..af4e4fa32b --- /dev/null +++ b/tests/components/a4988/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +stepper: + - platform: a4988 + id: a4988_stepper + step_pin: + number: 2 + dir_pin: + number: 3 + sleep_pin: + number: 5 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/a4988/test.esp32-idf.yaml b/tests/components/a4988/test.esp32-idf.yaml new file mode 100644 index 0000000000..0ca5e3f504 --- /dev/null +++ b/tests/components/a4988/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +stepper: + - platform: a4988 + id: a4988_stepper + step_pin: + number: 22 + dir_pin: + number: 23 + sleep_pin: + number: 25 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/a4988/test.esp32.yaml b/tests/components/a4988/test.esp32.yaml new file mode 100644 index 0000000000..0ca5e3f504 --- /dev/null +++ b/tests/components/a4988/test.esp32.yaml @@ -0,0 +1,12 @@ +stepper: + - platform: a4988 + id: a4988_stepper + step_pin: + number: 22 + dir_pin: + number: 23 + sleep_pin: + number: 25 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/a4988/test.esp8266.yaml b/tests/components/a4988/test.esp8266.yaml new file mode 100644 index 0000000000..f4c1886fc5 --- /dev/null +++ b/tests/components/a4988/test.esp8266.yaml @@ -0,0 +1,12 @@ +stepper: + - platform: a4988 + id: a4988_stepper + step_pin: + number: 1 + dir_pin: + number: 2 + sleep_pin: + number: 5 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/a4988/test.rp2040.yaml b/tests/components/a4988/test.rp2040.yaml new file mode 100644 index 0000000000..af4e4fa32b --- /dev/null +++ b/tests/components/a4988/test.rp2040.yaml @@ -0,0 +1,12 @@ +stepper: + - platform: a4988 + id: a4988_stepper + step_pin: + number: 2 + dir_pin: + number: 3 + sleep_pin: + number: 5 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/absolute_humidity/test.esp32-c3-idf.yaml b/tests/components/absolute_humidity/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..87a99f5206 --- /dev/null +++ b/tests/components/absolute_humidity/test.esp32-c3-idf.yaml @@ -0,0 +1,21 @@ +sensor: + - platform: absolute_humidity + name: Absolute Humidity + temperature: template_temperature + humidity: template_humidity + - platform: template + id: template_humidity + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } diff --git a/tests/components/absolute_humidity/test.esp32-c3.yaml b/tests/components/absolute_humidity/test.esp32-c3.yaml new file mode 100644 index 0000000000..87a99f5206 --- /dev/null +++ b/tests/components/absolute_humidity/test.esp32-c3.yaml @@ -0,0 +1,21 @@ +sensor: + - platform: absolute_humidity + name: Absolute Humidity + temperature: template_temperature + humidity: template_humidity + - platform: template + id: template_humidity + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } diff --git a/tests/components/absolute_humidity/test.esp32-idf.yaml b/tests/components/absolute_humidity/test.esp32-idf.yaml new file mode 100644 index 0000000000..87a99f5206 --- /dev/null +++ b/tests/components/absolute_humidity/test.esp32-idf.yaml @@ -0,0 +1,21 @@ +sensor: + - platform: absolute_humidity + name: Absolute Humidity + temperature: template_temperature + humidity: template_humidity + - platform: template + id: template_humidity + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } diff --git a/tests/components/absolute_humidity/test.esp32.yaml b/tests/components/absolute_humidity/test.esp32.yaml new file mode 100644 index 0000000000..87a99f5206 --- /dev/null +++ b/tests/components/absolute_humidity/test.esp32.yaml @@ -0,0 +1,21 @@ +sensor: + - platform: absolute_humidity + name: Absolute Humidity + temperature: template_temperature + humidity: template_humidity + - platform: template + id: template_humidity + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } diff --git a/tests/components/absolute_humidity/test.esp8266.yaml b/tests/components/absolute_humidity/test.esp8266.yaml new file mode 100644 index 0000000000..87a99f5206 --- /dev/null +++ b/tests/components/absolute_humidity/test.esp8266.yaml @@ -0,0 +1,21 @@ +sensor: + - platform: absolute_humidity + name: Absolute Humidity + temperature: template_temperature + humidity: template_humidity + - platform: template + id: template_humidity + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } diff --git a/tests/components/absolute_humidity/test.rp2040.yaml b/tests/components/absolute_humidity/test.rp2040.yaml new file mode 100644 index 0000000000..87a99f5206 --- /dev/null +++ b/tests/components/absolute_humidity/test.rp2040.yaml @@ -0,0 +1,21 @@ +sensor: + - platform: absolute_humidity + name: Absolute Humidity + temperature: template_temperature + humidity: template_humidity + - platform: template + id: template_humidity + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } diff --git a/tests/components/ac_dimmer/test.esp32-c3.yaml b/tests/components/ac_dimmer/test.esp32-c3.yaml new file mode 100644 index 0000000000..f411d376be --- /dev/null +++ b/tests/components/ac_dimmer/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +output: + - platform: ac_dimmer + id: ac_dimmer_1 + gate_pin: + number: 5 + zero_cross_pin: + number: 6 diff --git a/tests/components/ac_dimmer/test.esp32.yaml b/tests/components/ac_dimmer/test.esp32.yaml new file mode 100644 index 0000000000..cc17201666 --- /dev/null +++ b/tests/components/ac_dimmer/test.esp32.yaml @@ -0,0 +1,7 @@ +output: + - platform: ac_dimmer + id: ac_dimmer_1 + gate_pin: + number: 12 + zero_cross_pin: + number: 13 diff --git a/tests/components/ac_dimmer/test.esp8266.yaml b/tests/components/ac_dimmer/test.esp8266.yaml new file mode 100644 index 0000000000..af18d11c5f --- /dev/null +++ b/tests/components/ac_dimmer/test.esp8266.yaml @@ -0,0 +1,7 @@ +output: + - platform: ac_dimmer + id: ac_dimmer_1 + gate_pin: + number: 5 + zero_cross_pin: + number: 4 diff --git a/tests/components/ac_dimmer/test.rp2040.yaml b/tests/components/ac_dimmer/test.rp2040.yaml new file mode 100644 index 0000000000..f411d376be --- /dev/null +++ b/tests/components/ac_dimmer/test.rp2040.yaml @@ -0,0 +1,7 @@ +output: + - platform: ac_dimmer + id: ac_dimmer_1 + gate_pin: + number: 5 + zero_cross_pin: + number: 6 diff --git a/tests/components/adc/test.esp32-c3.yaml b/tests/components/adc/test.esp32-c3.yaml new file mode 100644 index 0000000000..18e5ab3561 --- /dev/null +++ b/tests/components/adc/test.esp32-c3.yaml @@ -0,0 +1,5 @@ +sensor: + - platform: adc + id: my_sensor + pin: 4 + attenuation: 11db diff --git a/tests/components/adc/test.esp32-idf.yaml b/tests/components/adc/test.esp32-idf.yaml new file mode 100644 index 0000000000..923fd0d706 --- /dev/null +++ b/tests/components/adc/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: adc + pin: A0 + name: Living Room Brightness + update_interval: "1:01" + attenuation: 2.5db + unit_of_measurement: "°C" + icon: "mdi:water-percent" + accuracy_decimals: 5 + setup_priority: -100 + force_update: true diff --git a/tests/components/adc/test.esp32-s2.yaml b/tests/components/adc/test.esp32-s2.yaml new file mode 100644 index 0000000000..0119ad5e4d --- /dev/null +++ b/tests/components/adc/test.esp32-s2.yaml @@ -0,0 +1,5 @@ +sensor: + - platform: adc + id: my_sensor + pin: 1 + attenuation: 11db diff --git a/tests/components/adc/test.esp32-s3.yaml b/tests/components/adc/test.esp32-s3.yaml new file mode 100644 index 0000000000..0119ad5e4d --- /dev/null +++ b/tests/components/adc/test.esp32-s3.yaml @@ -0,0 +1,5 @@ +sensor: + - platform: adc + id: my_sensor + pin: 1 + attenuation: 11db diff --git a/tests/components/adc/test.esp32.yaml b/tests/components/adc/test.esp32.yaml new file mode 100644 index 0000000000..923fd0d706 --- /dev/null +++ b/tests/components/adc/test.esp32.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: adc + pin: A0 + name: Living Room Brightness + update_interval: "1:01" + attenuation: 2.5db + unit_of_measurement: "°C" + icon: "mdi:water-percent" + accuracy_decimals: 5 + setup_priority: -100 + force_update: true diff --git a/tests/components/adc/test.esp8266.yaml b/tests/components/adc/test.esp8266.yaml new file mode 100644 index 0000000000..1ef79c7ca1 --- /dev/null +++ b/tests/components/adc/test.esp8266.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: adc + id: my_sensor + pin: VCC diff --git a/tests/components/adc/test.rp2040.yaml b/tests/components/adc/test.rp2040.yaml new file mode 100644 index 0000000000..200b802a4d --- /dev/null +++ b/tests/components/adc/test.rp2040.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: adc + pin: VCC + name: VSYS diff --git a/tests/components/adc128s102/test.esp32-c3-idf.yaml b/tests/components/adc128s102/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..8edf745e58 --- /dev/null +++ b/tests/components/adc128s102/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_adc128s102 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +adc128s102: + cs_pin: 8 + id: adc128s102_adc + +sensor: + - platform: adc128s102 + id: adc128s102_channel_0 + channel: 0 diff --git a/tests/components/adc128s102/test.esp32-c3.yaml b/tests/components/adc128s102/test.esp32-c3.yaml new file mode 100644 index 0000000000..8edf745e58 --- /dev/null +++ b/tests/components/adc128s102/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_adc128s102 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +adc128s102: + cs_pin: 8 + id: adc128s102_adc + +sensor: + - platform: adc128s102 + id: adc128s102_channel_0 + channel: 0 diff --git a/tests/components/adc128s102/test.esp32-idf.yaml b/tests/components/adc128s102/test.esp32-idf.yaml new file mode 100644 index 0000000000..005fbccc34 --- /dev/null +++ b/tests/components/adc128s102/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_adc128s102 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +adc128s102: + cs_pin: 12 + id: adc128s102_adc + +sensor: + - platform: adc128s102 + id: adc128s102_channel_0 + channel: 0 diff --git a/tests/components/adc128s102/test.esp32.yaml b/tests/components/adc128s102/test.esp32.yaml new file mode 100644 index 0000000000..005fbccc34 --- /dev/null +++ b/tests/components/adc128s102/test.esp32.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_adc128s102 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +adc128s102: + cs_pin: 12 + id: adc128s102_adc + +sensor: + - platform: adc128s102 + id: adc128s102_channel_0 + channel: 0 diff --git a/tests/components/adc128s102/test.esp8266.yaml b/tests/components/adc128s102/test.esp8266.yaml new file mode 100644 index 0000000000..09a51caec1 --- /dev/null +++ b/tests/components/adc128s102/test.esp8266.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_adc128s102 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +adc128s102: + cs_pin: 15 + id: adc128s102_adc + +sensor: + - platform: adc128s102 + id: adc128s102_channel_0 + channel: 0 diff --git a/tests/components/adc128s102/test.rp2040.yaml b/tests/components/adc128s102/test.rp2040.yaml new file mode 100644 index 0000000000..a7d54cbfe6 --- /dev/null +++ b/tests/components/adc128s102/test.rp2040.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_adc128s102 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +adc128s102: + cs_pin: 5 + id: adc128s102_adc + +sensor: + - platform: adc128s102 + id: adc128s102_channel_0 + channel: 0 diff --git a/tests/components/addressable_light/test.esp32-c3-idf.yaml b/tests/components/addressable_light/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..f587113fac --- /dev/null +++ b/tests/components/addressable_light/test.esp32-c3-idf.yaml @@ -0,0 +1,31 @@ +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + +display: + - platform: addressable_light + id: led_matrix_32x8_display + addressable_light_id: led_matrix_32x8 + width: 32 + height: 8 + pixel_mapper: |- + if (x % 2 == 0) { + return (x * 8) + y; + } + return (x * 8) + (7 - y); + lambda: |- + Color red = Color(0xFF0000); + Color green = Color(0x00FF00); + Color blue = Color(0x0000FF); + it.rectangle(0, 0, it.get_width(), it.get_height(), red); + it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green); + it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue); + it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); + rotation: 0° + update_interval: 16ms diff --git a/tests/components/addressable_light/test.esp32-c3.yaml b/tests/components/addressable_light/test.esp32-c3.yaml new file mode 100644 index 0000000000..f587113fac --- /dev/null +++ b/tests/components/addressable_light/test.esp32-c3.yaml @@ -0,0 +1,31 @@ +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + +display: + - platform: addressable_light + id: led_matrix_32x8_display + addressable_light_id: led_matrix_32x8 + width: 32 + height: 8 + pixel_mapper: |- + if (x % 2 == 0) { + return (x * 8) + y; + } + return (x * 8) + (7 - y); + lambda: |- + Color red = Color(0xFF0000); + Color green = Color(0x00FF00); + Color blue = Color(0x0000FF); + it.rectangle(0, 0, it.get_width(), it.get_height(), red); + it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green); + it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue); + it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); + rotation: 0° + update_interval: 16ms diff --git a/tests/components/addressable_light/test.esp32-idf.yaml b/tests/components/addressable_light/test.esp32-idf.yaml new file mode 100644 index 0000000000..f587113fac --- /dev/null +++ b/tests/components/addressable_light/test.esp32-idf.yaml @@ -0,0 +1,31 @@ +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + +display: + - platform: addressable_light + id: led_matrix_32x8_display + addressable_light_id: led_matrix_32x8 + width: 32 + height: 8 + pixel_mapper: |- + if (x % 2 == 0) { + return (x * 8) + y; + } + return (x * 8) + (7 - y); + lambda: |- + Color red = Color(0xFF0000); + Color green = Color(0x00FF00); + Color blue = Color(0x0000FF); + it.rectangle(0, 0, it.get_width(), it.get_height(), red); + it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green); + it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue); + it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); + rotation: 0° + update_interval: 16ms diff --git a/tests/components/addressable_light/test.esp32.yaml b/tests/components/addressable_light/test.esp32.yaml new file mode 100644 index 0000000000..f7717be610 --- /dev/null +++ b/tests/components/addressable_light/test.esp32.yaml @@ -0,0 +1,32 @@ +light: + - platform: fastled_clockless + id: led_matrix_32x8 + name: led_matrix_32x8 + chipset: WS2812B + pin: 2 + num_leds: 256 + rgb_order: GRB + default_transition_length: 0s + color_correct: [50%, 50%, 50%] + +display: + - platform: addressable_light + id: led_matrix_32x8_display + addressable_light_id: led_matrix_32x8 + width: 32 + height: 8 + pixel_mapper: |- + if (x % 2 == 0) { + return (x * 8) + y; + } + return (x * 8) + (7 - y); + lambda: |- + Color red = Color(0xFF0000); + Color green = Color(0x00FF00); + Color blue = Color(0x0000FF); + it.rectangle(0, 0, it.get_width(), it.get_height(), red); + it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green); + it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue); + it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); + rotation: 0° + update_interval: 16ms diff --git a/tests/components/ade7880/common.yaml b/tests/components/ade7880/common.yaml new file mode 100644 index 0000000000..0aa388a325 --- /dev/null +++ b/tests/components/ade7880/common.yaml @@ -0,0 +1,56 @@ +i2c: + - id: i2c_ade7880 + scl: ${scl_pin} + sda: ${sda_pin} + +sensor: + - platform: ade7880 + i2c_id: i2c_ade7880 + irq0_pin: ${irq0_pin} + irq1_pin: ${irq1_pin} + reset_pin: ${reset_pin} + frequency: 60Hz + phase_a: + name: Channel A + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3116628 + voltage_gain: -757178 + power_gain: -1344457 + phase_angle: 188 + phase_b: + name: Channel B + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3133655 + voltage_gain: -755235 + power_gain: -1345638 + phase_angle: 188 + phase_c: + name: Channel C + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3111158 + voltage_gain: -743813 + power_gain: -1351437 + phase_angle: 180 + neutral: + name: Neutral + current: Current + calibration: + current_gain: 3189 diff --git a/tests/components/ade7880/test.esp32-c3-idf.yaml b/tests/components/ade7880/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..87db3e9427 --- /dev/null +++ b/tests/components/ade7880/test.esp32-c3-idf.yaml @@ -0,0 +1,8 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + irq0_pin: GPIO6 + irq1_pin: GPIO7 + reset_pin: GPIO10 + +<<: !include common.yaml diff --git a/tests/components/ade7880/test.esp32-c3.yaml b/tests/components/ade7880/test.esp32-c3.yaml new file mode 100644 index 0000000000..87db3e9427 --- /dev/null +++ b/tests/components/ade7880/test.esp32-c3.yaml @@ -0,0 +1,8 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + irq0_pin: GPIO6 + irq1_pin: GPIO7 + reset_pin: GPIO10 + +<<: !include common.yaml diff --git a/tests/components/ade7880/test.esp32-idf.yaml b/tests/components/ade7880/test.esp32-idf.yaml new file mode 100644 index 0000000000..685b49ff32 --- /dev/null +++ b/tests/components/ade7880/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + irq0_pin: GPIO13 + irq1_pin: GPIO15 + reset_pin: GPIO16 + +<<: !include common.yaml diff --git a/tests/components/ade7880/test.esp32.yaml b/tests/components/ade7880/test.esp32.yaml new file mode 100644 index 0000000000..685b49ff32 --- /dev/null +++ b/tests/components/ade7880/test.esp32.yaml @@ -0,0 +1,8 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + irq0_pin: GPIO13 + irq1_pin: GPIO15 + reset_pin: GPIO16 + +<<: !include common.yaml diff --git a/tests/components/ade7880/test.esp8266.yaml b/tests/components/ade7880/test.esp8266.yaml new file mode 100644 index 0000000000..685b49ff32 --- /dev/null +++ b/tests/components/ade7880/test.esp8266.yaml @@ -0,0 +1,8 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + irq0_pin: GPIO13 + irq1_pin: GPIO15 + reset_pin: GPIO16 + +<<: !include common.yaml diff --git a/tests/components/ade7880/test.rp2040.yaml b/tests/components/ade7880/test.rp2040.yaml new file mode 100644 index 0000000000..685b49ff32 --- /dev/null +++ b/tests/components/ade7880/test.rp2040.yaml @@ -0,0 +1,8 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + irq0_pin: GPIO13 + irq1_pin: GPIO15 + reset_pin: GPIO16 + +<<: !include common.yaml diff --git a/tests/components/ade7953_i2c/test.esp32-c3-idf.yaml b/tests/components/ade7953_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..d7b365a7e1 --- /dev/null +++ b/tests/components/ade7953_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_i2c/test.esp32-c3.yaml b/tests/components/ade7953_i2c/test.esp32-c3.yaml new file mode 100644 index 0000000000..d7b365a7e1 --- /dev/null +++ b/tests/components/ade7953_i2c/test.esp32-c3.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_i2c/test.esp32-idf.yaml b/tests/components/ade7953_i2c/test.esp32-idf.yaml new file mode 100644 index 0000000000..71602f20a3 --- /dev/null +++ b/tests/components/ade7953_i2c/test.esp32-idf.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_ade7953 + scl: 16 + sda: 17 + +sensor: + - platform: ade7953_i2c + irq_pin: 15 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_i2c/test.esp32.yaml b/tests/components/ade7953_i2c/test.esp32.yaml new file mode 100644 index 0000000000..71602f20a3 --- /dev/null +++ b/tests/components/ade7953_i2c/test.esp32.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_ade7953 + scl: 16 + sda: 17 + +sensor: + - platform: ade7953_i2c + irq_pin: 15 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_i2c/test.esp8266.yaml b/tests/components/ade7953_i2c/test.esp8266.yaml new file mode 100644 index 0000000000..6903cd1953 --- /dev/null +++ b/tests/components/ade7953_i2c/test.esp8266.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 15 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_i2c/test.rp2040.yaml b/tests/components/ade7953_i2c/test.rp2040.yaml new file mode 100644 index 0000000000..d7b365a7e1 --- /dev/null +++ b/tests/components/ade7953_i2c/test.rp2040.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_spi/test.esp32-c3-idf.yaml b/tests/components/ade7953_spi/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..a967f28d9c --- /dev/null +++ b/tests/components/ade7953_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,36 @@ +spi: + - id: spi_ade7953 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: ade7953_spi + cs_pin: 8 + irq_pin: 9 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_spi/test.esp32-c3.yaml b/tests/components/ade7953_spi/test.esp32-c3.yaml new file mode 100644 index 0000000000..a967f28d9c --- /dev/null +++ b/tests/components/ade7953_spi/test.esp32-c3.yaml @@ -0,0 +1,36 @@ +spi: + - id: spi_ade7953 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: ade7953_spi + cs_pin: 8 + irq_pin: 9 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_spi/test.esp32-idf.yaml b/tests/components/ade7953_spi/test.esp32-idf.yaml new file mode 100644 index 0000000000..e9ef7e4116 --- /dev/null +++ b/tests/components/ade7953_spi/test.esp32-idf.yaml @@ -0,0 +1,36 @@ +spi: + - id: spi_ade7953 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: ade7953_spi + cs_pin: 5 + irq_pin: 13 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_spi/test.esp32.yaml b/tests/components/ade7953_spi/test.esp32.yaml new file mode 100644 index 0000000000..e9ef7e4116 --- /dev/null +++ b/tests/components/ade7953_spi/test.esp32.yaml @@ -0,0 +1,36 @@ +spi: + - id: spi_ade7953 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: ade7953_spi + cs_pin: 5 + irq_pin: 13 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_spi/test.esp8266.yaml b/tests/components/ade7953_spi/test.esp8266.yaml new file mode 100644 index 0000000000..b36b4445ab --- /dev/null +++ b/tests/components/ade7953_spi/test.esp8266.yaml @@ -0,0 +1,36 @@ +spi: + - id: spi_ade7953 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: ade7953_spi + cs_pin: 15 + irq_pin: 5 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_spi/test.rp2040.yaml b/tests/components/ade7953_spi/test.rp2040.yaml new file mode 100644 index 0000000000..319abd4613 --- /dev/null +++ b/tests/components/ade7953_spi/test.rp2040.yaml @@ -0,0 +1,36 @@ +spi: + - id: spi_ade7953 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: ade7953_spi + cs_pin: 5 + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ads1115/test.esp32-c3-idf.yaml b/tests/components/ads1115/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..7ac5a09f3f --- /dev/null +++ b/tests/components/ads1115/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ads1115 + scl: 5 + sda: 4 + +ads1115: + address: 0x48 + +sensor: + - platform: ads1115 + multiplexer: A0_A1 + gain: 1.024 + id: ads1115_sensor diff --git a/tests/components/ads1115/test.esp32-c3.yaml b/tests/components/ads1115/test.esp32-c3.yaml new file mode 100644 index 0000000000..7ac5a09f3f --- /dev/null +++ b/tests/components/ads1115/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ads1115 + scl: 5 + sda: 4 + +ads1115: + address: 0x48 + +sensor: + - platform: ads1115 + multiplexer: A0_A1 + gain: 1.024 + id: ads1115_sensor diff --git a/tests/components/ads1115/test.esp32-idf.yaml b/tests/components/ads1115/test.esp32-idf.yaml new file mode 100644 index 0000000000..a869f2379b --- /dev/null +++ b/tests/components/ads1115/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ads1115 + scl: 16 + sda: 17 + +ads1115: + address: 0x48 + +sensor: + - platform: ads1115 + multiplexer: A0_A1 + gain: 1.024 + id: ads1115_sensor diff --git a/tests/components/ads1115/test.esp32.yaml b/tests/components/ads1115/test.esp32.yaml new file mode 100644 index 0000000000..a869f2379b --- /dev/null +++ b/tests/components/ads1115/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ads1115 + scl: 16 + sda: 17 + +ads1115: + address: 0x48 + +sensor: + - platform: ads1115 + multiplexer: A0_A1 + gain: 1.024 + id: ads1115_sensor diff --git a/tests/components/ads1115/test.esp8266.yaml b/tests/components/ads1115/test.esp8266.yaml new file mode 100644 index 0000000000..7ac5a09f3f --- /dev/null +++ b/tests/components/ads1115/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ads1115 + scl: 5 + sda: 4 + +ads1115: + address: 0x48 + +sensor: + - platform: ads1115 + multiplexer: A0_A1 + gain: 1.024 + id: ads1115_sensor diff --git a/tests/components/ads1115/test.rp2040.yaml b/tests/components/ads1115/test.rp2040.yaml new file mode 100644 index 0000000000..7ac5a09f3f --- /dev/null +++ b/tests/components/ads1115/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ads1115 + scl: 5 + sda: 4 + +ads1115: + address: 0x48 + +sensor: + - platform: ads1115 + multiplexer: A0_A1 + gain: 1.024 + id: ads1115_sensor diff --git a/tests/components/ags10/test.esp32-c3-idf.yaml b/tests/components/ags10/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..e338fc78e0 --- /dev/null +++ b/tests/components/ags10/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ags10 + scl: 5 + sda: 4 + frequency: 10kHz + +sensor: + - platform: ags10 + id: ags10_1 + tvoc: + name: AGS10 TVOC + update_interval: 60s diff --git a/tests/components/ags10/test.esp32-c3.yaml b/tests/components/ags10/test.esp32-c3.yaml new file mode 100644 index 0000000000..e338fc78e0 --- /dev/null +++ b/tests/components/ags10/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ags10 + scl: 5 + sda: 4 + frequency: 10kHz + +sensor: + - platform: ags10 + id: ags10_1 + tvoc: + name: AGS10 TVOC + update_interval: 60s diff --git a/tests/components/ags10/test.esp32-idf.yaml b/tests/components/ags10/test.esp32-idf.yaml new file mode 100644 index 0000000000..b3b53c0d31 --- /dev/null +++ b/tests/components/ags10/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ags10 + scl: 16 + sda: 17 + frequency: 10kHz + +sensor: + - platform: ags10 + id: ags10_1 + tvoc: + name: AGS10 TVOC + update_interval: 60s diff --git a/tests/components/ags10/test.esp32.yaml b/tests/components/ags10/test.esp32.yaml new file mode 100644 index 0000000000..b3b53c0d31 --- /dev/null +++ b/tests/components/ags10/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ags10 + scl: 16 + sda: 17 + frequency: 10kHz + +sensor: + - platform: ags10 + id: ags10_1 + tvoc: + name: AGS10 TVOC + update_interval: 60s diff --git a/tests/components/ags10/test.esp8266.yaml b/tests/components/ags10/test.esp8266.yaml new file mode 100644 index 0000000000..e338fc78e0 --- /dev/null +++ b/tests/components/ags10/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ags10 + scl: 5 + sda: 4 + frequency: 10kHz + +sensor: + - platform: ags10 + id: ags10_1 + tvoc: + name: AGS10 TVOC + update_interval: 60s diff --git a/tests/components/aht10/test.esp32-c3-idf.yaml b/tests/components/aht10/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..2e5f505476 --- /dev/null +++ b/tests/components/aht10/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_aht10 + scl: 5 + sda: 4 + +sensor: + - platform: aht10 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/aht10/test.esp32-c3.yaml b/tests/components/aht10/test.esp32-c3.yaml new file mode 100644 index 0000000000..2e5f505476 --- /dev/null +++ b/tests/components/aht10/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_aht10 + scl: 5 + sda: 4 + +sensor: + - platform: aht10 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/aht10/test.esp32-idf.yaml b/tests/components/aht10/test.esp32-idf.yaml new file mode 100644 index 0000000000..499e69e5d3 --- /dev/null +++ b/tests/components/aht10/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_aht10 + scl: 16 + sda: 17 + +sensor: + - platform: aht10 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/aht10/test.esp32.yaml b/tests/components/aht10/test.esp32.yaml new file mode 100644 index 0000000000..499e69e5d3 --- /dev/null +++ b/tests/components/aht10/test.esp32.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_aht10 + scl: 16 + sda: 17 + +sensor: + - platform: aht10 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/aht10/test.esp8266.yaml b/tests/components/aht10/test.esp8266.yaml new file mode 100644 index 0000000000..2e5f505476 --- /dev/null +++ b/tests/components/aht10/test.esp8266.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_aht10 + scl: 5 + sda: 4 + +sensor: + - platform: aht10 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/aht10/test.rp2040.yaml b/tests/components/aht10/test.rp2040.yaml new file mode 100644 index 0000000000..2e5f505476 --- /dev/null +++ b/tests/components/aht10/test.rp2040.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_aht10 + scl: 5 + sda: 4 + +sensor: + - platform: aht10 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/airthings_wave_mini/test.esp32-c3-idf.yaml b/tests/components/airthings_wave_mini/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..87902e6c66 --- /dev/null +++ b/tests/components/airthings_wave_mini/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: airthingsmini01 + +sensor: + - id: airthingswm + platform: airthings_wave_mini + ble_client_id: airthingsmini01 + update_interval: 5min + battery_update_interval: 12h + temperature: + name: Wave Mini Temperature + humidity: + name: Wave Mini Humidity + pressure: + name: Wave Mini Pressure + tvoc: + name: Wave Mini VOC + battery_voltage: + name: Wave Mini Battery Voltage diff --git a/tests/components/airthings_wave_mini/test.esp32-c3.yaml b/tests/components/airthings_wave_mini/test.esp32-c3.yaml new file mode 100644 index 0000000000..87902e6c66 --- /dev/null +++ b/tests/components/airthings_wave_mini/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: airthingsmini01 + +sensor: + - id: airthingswm + platform: airthings_wave_mini + ble_client_id: airthingsmini01 + update_interval: 5min + battery_update_interval: 12h + temperature: + name: Wave Mini Temperature + humidity: + name: Wave Mini Humidity + pressure: + name: Wave Mini Pressure + tvoc: + name: Wave Mini VOC + battery_voltage: + name: Wave Mini Battery Voltage diff --git a/tests/components/airthings_wave_mini/test.esp32-idf.yaml b/tests/components/airthings_wave_mini/test.esp32-idf.yaml new file mode 100644 index 0000000000..87902e6c66 --- /dev/null +++ b/tests/components/airthings_wave_mini/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: airthingsmini01 + +sensor: + - id: airthingswm + platform: airthings_wave_mini + ble_client_id: airthingsmini01 + update_interval: 5min + battery_update_interval: 12h + temperature: + name: Wave Mini Temperature + humidity: + name: Wave Mini Humidity + pressure: + name: Wave Mini Pressure + tvoc: + name: Wave Mini VOC + battery_voltage: + name: Wave Mini Battery Voltage diff --git a/tests/components/airthings_wave_mini/test.esp32.yaml b/tests/components/airthings_wave_mini/test.esp32.yaml new file mode 100644 index 0000000000..87902e6c66 --- /dev/null +++ b/tests/components/airthings_wave_mini/test.esp32.yaml @@ -0,0 +1,22 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: airthingsmini01 + +sensor: + - id: airthingswm + platform: airthings_wave_mini + ble_client_id: airthingsmini01 + update_interval: 5min + battery_update_interval: 12h + temperature: + name: Wave Mini Temperature + humidity: + name: Wave Mini Humidity + pressure: + name: Wave Mini Pressure + tvoc: + name: Wave Mini VOC + battery_voltage: + name: Wave Mini Battery Voltage diff --git a/tests/components/airthings_wave_plus/test.esp32-c3-idf.yaml b/tests/components/airthings_wave_plus/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..2124fcdaec --- /dev/null +++ b/tests/components/airthings_wave_plus/test.esp32-c3-idf.yaml @@ -0,0 +1,28 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: airthings01 + +sensor: + - id: airthingswp + platform: airthings_wave_plus + ble_client_id: airthings01 + update_interval: 5min + battery_update_interval: 12h + temperature: + name: Wave Plus Temperature + radon: + name: Wave Plus Radon + radon_long_term: + name: Wave Plus Radon Long Term + pressure: + name: Wave Plus Pressure + humidity: + name: Wave Plus Humidity + co2: + name: Wave Plus CO2 + tvoc: + name: Wave Plus VOC + battery_voltage: + name: Wave Plus Battery Voltage diff --git a/tests/components/airthings_wave_plus/test.esp32-c3.yaml b/tests/components/airthings_wave_plus/test.esp32-c3.yaml new file mode 100644 index 0000000000..2124fcdaec --- /dev/null +++ b/tests/components/airthings_wave_plus/test.esp32-c3.yaml @@ -0,0 +1,28 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: airthings01 + +sensor: + - id: airthingswp + platform: airthings_wave_plus + ble_client_id: airthings01 + update_interval: 5min + battery_update_interval: 12h + temperature: + name: Wave Plus Temperature + radon: + name: Wave Plus Radon + radon_long_term: + name: Wave Plus Radon Long Term + pressure: + name: Wave Plus Pressure + humidity: + name: Wave Plus Humidity + co2: + name: Wave Plus CO2 + tvoc: + name: Wave Plus VOC + battery_voltage: + name: Wave Plus Battery Voltage diff --git a/tests/components/airthings_wave_plus/test.esp32-idf.yaml b/tests/components/airthings_wave_plus/test.esp32-idf.yaml new file mode 100644 index 0000000000..2124fcdaec --- /dev/null +++ b/tests/components/airthings_wave_plus/test.esp32-idf.yaml @@ -0,0 +1,28 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: airthings01 + +sensor: + - id: airthingswp + platform: airthings_wave_plus + ble_client_id: airthings01 + update_interval: 5min + battery_update_interval: 12h + temperature: + name: Wave Plus Temperature + radon: + name: Wave Plus Radon + radon_long_term: + name: Wave Plus Radon Long Term + pressure: + name: Wave Plus Pressure + humidity: + name: Wave Plus Humidity + co2: + name: Wave Plus CO2 + tvoc: + name: Wave Plus VOC + battery_voltage: + name: Wave Plus Battery Voltage diff --git a/tests/components/airthings_wave_plus/test.esp32.yaml b/tests/components/airthings_wave_plus/test.esp32.yaml new file mode 100644 index 0000000000..2124fcdaec --- /dev/null +++ b/tests/components/airthings_wave_plus/test.esp32.yaml @@ -0,0 +1,28 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: airthings01 + +sensor: + - id: airthingswp + platform: airthings_wave_plus + ble_client_id: airthings01 + update_interval: 5min + battery_update_interval: 12h + temperature: + name: Wave Plus Temperature + radon: + name: Wave Plus Radon + radon_long_term: + name: Wave Plus Radon Long Term + pressure: + name: Wave Plus Pressure + humidity: + name: Wave Plus Humidity + co2: + name: Wave Plus CO2 + tvoc: + name: Wave Plus VOC + battery_voltage: + name: Wave Plus Battery Voltage diff --git a/tests/components/alarm_control_panel/test.esp32-c3-idf.yaml b/tests/components/alarm_control_panel/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..218274bad4 --- /dev/null +++ b/tests/components/alarm_control_panel/test.esp32-c3-idf.yaml @@ -0,0 +1,64 @@ +binary_sensor: + - platform: gpio + id: bin1 + pin: 1 + +alarm_control_panel: + - platform: template + id: alarmcontrolpanel1 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_state: + then: + - lambda: !lambda |- + ESP_LOGD("TEST", "State change %s", LOG_STR_ARG(alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()))); + - platform: template + id: alarmcontrolpanel2 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_disarmed: + then: + - logger.log: "### DISARMED ###" + on_pending: + then: + - logger.log: "### PENDING ###" + on_arming: + then: + - logger.log: "### ARMING ###" + on_armed_home: + then: + - logger.log: "### ARMED HOME ###" + on_armed_night: + then: + - logger.log: "### ARMED NIGHT ###" + on_armed_away: + then: + - logger.log: "### ARMED AWAY ###" + on_triggered: + then: + - logger.log: "### TRIGGERED ###" + on_cleared: + then: + - logger.log: "### CLEARED ###" diff --git a/tests/components/alarm_control_panel/test.esp32-c3.yaml b/tests/components/alarm_control_panel/test.esp32-c3.yaml new file mode 100644 index 0000000000..218274bad4 --- /dev/null +++ b/tests/components/alarm_control_panel/test.esp32-c3.yaml @@ -0,0 +1,64 @@ +binary_sensor: + - platform: gpio + id: bin1 + pin: 1 + +alarm_control_panel: + - platform: template + id: alarmcontrolpanel1 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_state: + then: + - lambda: !lambda |- + ESP_LOGD("TEST", "State change %s", LOG_STR_ARG(alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()))); + - platform: template + id: alarmcontrolpanel2 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_disarmed: + then: + - logger.log: "### DISARMED ###" + on_pending: + then: + - logger.log: "### PENDING ###" + on_arming: + then: + - logger.log: "### ARMING ###" + on_armed_home: + then: + - logger.log: "### ARMED HOME ###" + on_armed_night: + then: + - logger.log: "### ARMED NIGHT ###" + on_armed_away: + then: + - logger.log: "### ARMED AWAY ###" + on_triggered: + then: + - logger.log: "### TRIGGERED ###" + on_cleared: + then: + - logger.log: "### CLEARED ###" diff --git a/tests/components/alarm_control_panel/test.esp32-idf.yaml b/tests/components/alarm_control_panel/test.esp32-idf.yaml new file mode 100644 index 0000000000..218274bad4 --- /dev/null +++ b/tests/components/alarm_control_panel/test.esp32-idf.yaml @@ -0,0 +1,64 @@ +binary_sensor: + - platform: gpio + id: bin1 + pin: 1 + +alarm_control_panel: + - platform: template + id: alarmcontrolpanel1 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_state: + then: + - lambda: !lambda |- + ESP_LOGD("TEST", "State change %s", LOG_STR_ARG(alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()))); + - platform: template + id: alarmcontrolpanel2 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_disarmed: + then: + - logger.log: "### DISARMED ###" + on_pending: + then: + - logger.log: "### PENDING ###" + on_arming: + then: + - logger.log: "### ARMING ###" + on_armed_home: + then: + - logger.log: "### ARMED HOME ###" + on_armed_night: + then: + - logger.log: "### ARMED NIGHT ###" + on_armed_away: + then: + - logger.log: "### ARMED AWAY ###" + on_triggered: + then: + - logger.log: "### TRIGGERED ###" + on_cleared: + then: + - logger.log: "### CLEARED ###" diff --git a/tests/components/alarm_control_panel/test.esp32.yaml b/tests/components/alarm_control_panel/test.esp32.yaml new file mode 100644 index 0000000000..218274bad4 --- /dev/null +++ b/tests/components/alarm_control_panel/test.esp32.yaml @@ -0,0 +1,64 @@ +binary_sensor: + - platform: gpio + id: bin1 + pin: 1 + +alarm_control_panel: + - platform: template + id: alarmcontrolpanel1 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_state: + then: + - lambda: !lambda |- + ESP_LOGD("TEST", "State change %s", LOG_STR_ARG(alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()))); + - platform: template + id: alarmcontrolpanel2 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_disarmed: + then: + - logger.log: "### DISARMED ###" + on_pending: + then: + - logger.log: "### PENDING ###" + on_arming: + then: + - logger.log: "### ARMING ###" + on_armed_home: + then: + - logger.log: "### ARMED HOME ###" + on_armed_night: + then: + - logger.log: "### ARMED NIGHT ###" + on_armed_away: + then: + - logger.log: "### ARMED AWAY ###" + on_triggered: + then: + - logger.log: "### TRIGGERED ###" + on_cleared: + then: + - logger.log: "### CLEARED ###" diff --git a/tests/components/alarm_control_panel/test.esp8266.yaml b/tests/components/alarm_control_panel/test.esp8266.yaml new file mode 100644 index 0000000000..218274bad4 --- /dev/null +++ b/tests/components/alarm_control_panel/test.esp8266.yaml @@ -0,0 +1,64 @@ +binary_sensor: + - platform: gpio + id: bin1 + pin: 1 + +alarm_control_panel: + - platform: template + id: alarmcontrolpanel1 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_state: + then: + - lambda: !lambda |- + ESP_LOGD("TEST", "State change %s", LOG_STR_ARG(alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()))); + - platform: template + id: alarmcontrolpanel2 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_disarmed: + then: + - logger.log: "### DISARMED ###" + on_pending: + then: + - logger.log: "### PENDING ###" + on_arming: + then: + - logger.log: "### ARMING ###" + on_armed_home: + then: + - logger.log: "### ARMED HOME ###" + on_armed_night: + then: + - logger.log: "### ARMED NIGHT ###" + on_armed_away: + then: + - logger.log: "### ARMED AWAY ###" + on_triggered: + then: + - logger.log: "### TRIGGERED ###" + on_cleared: + then: + - logger.log: "### CLEARED ###" diff --git a/tests/components/alarm_control_panel/test.rp2040.yaml b/tests/components/alarm_control_panel/test.rp2040.yaml new file mode 100644 index 0000000000..218274bad4 --- /dev/null +++ b/tests/components/alarm_control_panel/test.rp2040.yaml @@ -0,0 +1,64 @@ +binary_sensor: + - platform: gpio + id: bin1 + pin: 1 + +alarm_control_panel: + - platform: template + id: alarmcontrolpanel1 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_state: + then: + - lambda: !lambda |- + ESP_LOGD("TEST", "State change %s", LOG_STR_ARG(alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()))); + - platform: template + id: alarmcontrolpanel2 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_disarmed: + then: + - logger.log: "### DISARMED ###" + on_pending: + then: + - logger.log: "### PENDING ###" + on_arming: + then: + - logger.log: "### ARMING ###" + on_armed_home: + then: + - logger.log: "### ARMED HOME ###" + on_armed_night: + then: + - logger.log: "### ARMED NIGHT ###" + on_armed_away: + then: + - logger.log: "### ARMED AWAY ###" + on_triggered: + then: + - logger.log: "### TRIGGERED ###" + on_cleared: + then: + - logger.log: "### CLEARED ###" diff --git a/tests/components/alpha3/test.esp32-c3-idf.yaml b/tests/components/alpha3/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..913f086ac4 --- /dev/null +++ b/tests/components/alpha3/test.esp32-c3-idf.yaml @@ -0,0 +1,17 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: alpha3_blec + +sensor: + - platform: alpha3 + ble_client_id: alpha3_blec + flow: + name: "Radiator Pump Flow" + head: + name: "Radiator Pump Head" + power: + name: "Radiator Pump Power" + speed: + name: "Radiator Pump Speed" diff --git a/tests/components/alpha3/test.esp32-c3.yaml b/tests/components/alpha3/test.esp32-c3.yaml new file mode 100644 index 0000000000..913f086ac4 --- /dev/null +++ b/tests/components/alpha3/test.esp32-c3.yaml @@ -0,0 +1,17 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: alpha3_blec + +sensor: + - platform: alpha3 + ble_client_id: alpha3_blec + flow: + name: "Radiator Pump Flow" + head: + name: "Radiator Pump Head" + power: + name: "Radiator Pump Power" + speed: + name: "Radiator Pump Speed" diff --git a/tests/components/alpha3/test.esp32-idf.yaml b/tests/components/alpha3/test.esp32-idf.yaml new file mode 100644 index 0000000000..913f086ac4 --- /dev/null +++ b/tests/components/alpha3/test.esp32-idf.yaml @@ -0,0 +1,17 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: alpha3_blec + +sensor: + - platform: alpha3 + ble_client_id: alpha3_blec + flow: + name: "Radiator Pump Flow" + head: + name: "Radiator Pump Head" + power: + name: "Radiator Pump Power" + speed: + name: "Radiator Pump Speed" diff --git a/tests/components/alpha3/test.esp32.yaml b/tests/components/alpha3/test.esp32.yaml new file mode 100644 index 0000000000..913f086ac4 --- /dev/null +++ b/tests/components/alpha3/test.esp32.yaml @@ -0,0 +1,17 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: alpha3_blec + +sensor: + - platform: alpha3 + ble_client_id: alpha3_blec + flow: + name: "Radiator Pump Flow" + head: + name: "Radiator Pump Head" + power: + name: "Radiator Pump Power" + speed: + name: "Radiator Pump Speed" diff --git a/tests/components/am2315c/test.esp32-c3-idf.yaml b/tests/components/am2315c/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..d09bffb7a4 --- /dev/null +++ b/tests/components/am2315c/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_am2315c + scl: 5 + sda: 4 + +sensor: + - platform: am2315c + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2315c/test.esp32-c3.yaml b/tests/components/am2315c/test.esp32-c3.yaml new file mode 100644 index 0000000000..d09bffb7a4 --- /dev/null +++ b/tests/components/am2315c/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_am2315c + scl: 5 + sda: 4 + +sensor: + - platform: am2315c + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2315c/test.esp32-idf.yaml b/tests/components/am2315c/test.esp32-idf.yaml new file mode 100644 index 0000000000..ed6b65f787 --- /dev/null +++ b/tests/components/am2315c/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_am2315c + scl: 16 + sda: 17 + +sensor: + - platform: am2315c + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2315c/test.esp32.yaml b/tests/components/am2315c/test.esp32.yaml new file mode 100644 index 0000000000..ed6b65f787 --- /dev/null +++ b/tests/components/am2315c/test.esp32.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_am2315c + scl: 16 + sda: 17 + +sensor: + - platform: am2315c + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2315c/test.esp8266.yaml b/tests/components/am2315c/test.esp8266.yaml new file mode 100644 index 0000000000..d09bffb7a4 --- /dev/null +++ b/tests/components/am2315c/test.esp8266.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_am2315c + scl: 5 + sda: 4 + +sensor: + - platform: am2315c + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2315c/test.rp2040.yaml b/tests/components/am2315c/test.rp2040.yaml new file mode 100644 index 0000000000..d09bffb7a4 --- /dev/null +++ b/tests/components/am2315c/test.rp2040.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_am2315c + scl: 5 + sda: 4 + +sensor: + - platform: am2315c + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2320/test.esp32-c3-idf.yaml b/tests/components/am2320/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..6acfe8d4fd --- /dev/null +++ b/tests/components/am2320/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +sensor: + - platform: am2320 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2320/test.esp32-c3.yaml b/tests/components/am2320/test.esp32-c3.yaml new file mode 100644 index 0000000000..6acfe8d4fd --- /dev/null +++ b/tests/components/am2320/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +sensor: + - platform: am2320 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2320/test.esp32-idf.yaml b/tests/components/am2320/test.esp32-idf.yaml new file mode 100644 index 0000000000..99f4173b85 --- /dev/null +++ b/tests/components/am2320/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_bme280 + scl: 16 + sda: 17 + +sensor: + - platform: am2320 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2320/test.esp32.yaml b/tests/components/am2320/test.esp32.yaml new file mode 100644 index 0000000000..99f4173b85 --- /dev/null +++ b/tests/components/am2320/test.esp32.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_bme280 + scl: 16 + sda: 17 + +sensor: + - platform: am2320 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2320/test.esp8266.yaml b/tests/components/am2320/test.esp8266.yaml new file mode 100644 index 0000000000..6acfe8d4fd --- /dev/null +++ b/tests/components/am2320/test.esp8266.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +sensor: + - platform: am2320 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2320/test.rp2040.yaml b/tests/components/am2320/test.rp2040.yaml new file mode 100644 index 0000000000..6acfe8d4fd --- /dev/null +++ b/tests/components/am2320/test.rp2040.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +sensor: + - platform: am2320 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am43/test.esp32-c3-idf.yaml b/tests/components/am43/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..60b7d81a55 --- /dev/null +++ b/tests/components/am43/test.esp32-c3-idf.yaml @@ -0,0 +1,19 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: am43_blec + +cover: + - platform: am43 + name: Test AM43 Cover + id: am43_test + ble_client_id: am43_blec + +sensor: + - platform: am43 + ble_client_id: am43_blec + battery_level: + name: Kitchen blinds battery + illuminance: + name: Kitchen blinds light diff --git a/tests/components/am43/test.esp32-c3.yaml b/tests/components/am43/test.esp32-c3.yaml new file mode 100644 index 0000000000..60b7d81a55 --- /dev/null +++ b/tests/components/am43/test.esp32-c3.yaml @@ -0,0 +1,19 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: am43_blec + +cover: + - platform: am43 + name: Test AM43 Cover + id: am43_test + ble_client_id: am43_blec + +sensor: + - platform: am43 + ble_client_id: am43_blec + battery_level: + name: Kitchen blinds battery + illuminance: + name: Kitchen blinds light diff --git a/tests/components/am43/test.esp32-idf.yaml b/tests/components/am43/test.esp32-idf.yaml new file mode 100644 index 0000000000..60b7d81a55 --- /dev/null +++ b/tests/components/am43/test.esp32-idf.yaml @@ -0,0 +1,19 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: am43_blec + +cover: + - platform: am43 + name: Test AM43 Cover + id: am43_test + ble_client_id: am43_blec + +sensor: + - platform: am43 + ble_client_id: am43_blec + battery_level: + name: Kitchen blinds battery + illuminance: + name: Kitchen blinds light diff --git a/tests/components/am43/test.esp32.yaml b/tests/components/am43/test.esp32.yaml new file mode 100644 index 0000000000..60b7d81a55 --- /dev/null +++ b/tests/components/am43/test.esp32.yaml @@ -0,0 +1,19 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: am43_blec + +cover: + - platform: am43 + name: Test AM43 Cover + id: am43_test + ble_client_id: am43_blec + +sensor: + - platform: am43 + ble_client_id: am43_blec + battery_level: + name: Kitchen blinds battery + illuminance: + name: Kitchen blinds light diff --git a/tests/components/analog_threshold/test.esp32-c3-idf.yaml b/tests/components/analog_threshold/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..b5c14dfe56 --- /dev/null +++ b/tests/components/analog_threshold/test.esp32-c3-idf.yaml @@ -0,0 +1,28 @@ +sensor: + - platform: template + id: template_sensor + name: Template Sensor + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 15s + +binary_sensor: + - platform: analog_threshold + name: Analog Threshold 1 + sensor_id: template_sensor + threshold: + upper: 110 + lower: 90 + filters: + - delayed_on: 0s + - delayed_off: 10s + - platform: analog_threshold + name: Analog Threshold 2 + sensor_id: template_sensor + threshold: 100 + filters: + - invert: diff --git a/tests/components/analog_threshold/test.esp32-c3.yaml b/tests/components/analog_threshold/test.esp32-c3.yaml new file mode 100644 index 0000000000..b5c14dfe56 --- /dev/null +++ b/tests/components/analog_threshold/test.esp32-c3.yaml @@ -0,0 +1,28 @@ +sensor: + - platform: template + id: template_sensor + name: Template Sensor + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 15s + +binary_sensor: + - platform: analog_threshold + name: Analog Threshold 1 + sensor_id: template_sensor + threshold: + upper: 110 + lower: 90 + filters: + - delayed_on: 0s + - delayed_off: 10s + - platform: analog_threshold + name: Analog Threshold 2 + sensor_id: template_sensor + threshold: 100 + filters: + - invert: diff --git a/tests/components/analog_threshold/test.esp32-idf.yaml b/tests/components/analog_threshold/test.esp32-idf.yaml new file mode 100644 index 0000000000..b5c14dfe56 --- /dev/null +++ b/tests/components/analog_threshold/test.esp32-idf.yaml @@ -0,0 +1,28 @@ +sensor: + - platform: template + id: template_sensor + name: Template Sensor + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 15s + +binary_sensor: + - platform: analog_threshold + name: Analog Threshold 1 + sensor_id: template_sensor + threshold: + upper: 110 + lower: 90 + filters: + - delayed_on: 0s + - delayed_off: 10s + - platform: analog_threshold + name: Analog Threshold 2 + sensor_id: template_sensor + threshold: 100 + filters: + - invert: diff --git a/tests/components/analog_threshold/test.esp32.yaml b/tests/components/analog_threshold/test.esp32.yaml new file mode 100644 index 0000000000..b5c14dfe56 --- /dev/null +++ b/tests/components/analog_threshold/test.esp32.yaml @@ -0,0 +1,28 @@ +sensor: + - platform: template + id: template_sensor + name: Template Sensor + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 15s + +binary_sensor: + - platform: analog_threshold + name: Analog Threshold 1 + sensor_id: template_sensor + threshold: + upper: 110 + lower: 90 + filters: + - delayed_on: 0s + - delayed_off: 10s + - platform: analog_threshold + name: Analog Threshold 2 + sensor_id: template_sensor + threshold: 100 + filters: + - invert: diff --git a/tests/components/analog_threshold/test.esp8266.yaml b/tests/components/analog_threshold/test.esp8266.yaml new file mode 100644 index 0000000000..b5c14dfe56 --- /dev/null +++ b/tests/components/analog_threshold/test.esp8266.yaml @@ -0,0 +1,28 @@ +sensor: + - platform: template + id: template_sensor + name: Template Sensor + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 15s + +binary_sensor: + - platform: analog_threshold + name: Analog Threshold 1 + sensor_id: template_sensor + threshold: + upper: 110 + lower: 90 + filters: + - delayed_on: 0s + - delayed_off: 10s + - platform: analog_threshold + name: Analog Threshold 2 + sensor_id: template_sensor + threshold: 100 + filters: + - invert: diff --git a/tests/components/analog_threshold/test.rp2040.yaml b/tests/components/analog_threshold/test.rp2040.yaml new file mode 100644 index 0000000000..b5c14dfe56 --- /dev/null +++ b/tests/components/analog_threshold/test.rp2040.yaml @@ -0,0 +1,28 @@ +sensor: + - platform: template + id: template_sensor + name: Template Sensor + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 15s + +binary_sensor: + - platform: analog_threshold + name: Analog Threshold 1 + sensor_id: template_sensor + threshold: + upper: 110 + lower: 90 + filters: + - delayed_on: 0s + - delayed_off: 10s + - platform: analog_threshold + name: Analog Threshold 2 + sensor_id: template_sensor + threshold: 100 + filters: + - invert: diff --git a/tests/components/animation/test.esp32-c3-idf.yaml b/tests/components/animation/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..9bcfbdb118 --- /dev/null +++ b/tests/components/animation/test.esp32-c3-idf.yaml @@ -0,0 +1,23 @@ +spi: + - id: spi_main_lcd + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: ../../pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/components/animation/test.esp32-c3.yaml b/tests/components/animation/test.esp32-c3.yaml new file mode 100644 index 0000000000..9bcfbdb118 --- /dev/null +++ b/tests/components/animation/test.esp32-c3.yaml @@ -0,0 +1,23 @@ +spi: + - id: spi_main_lcd + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: ../../pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/components/animation/test.esp32-idf.yaml b/tests/components/animation/test.esp32-idf.yaml new file mode 100644 index 0000000000..5dc132eb2d --- /dev/null +++ b/tests/components/animation/test.esp32-idf.yaml @@ -0,0 +1,23 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 12 + dc_pin: 13 + reset_pin: 21 + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: ../../pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/components/animation/test.esp32.yaml b/tests/components/animation/test.esp32.yaml new file mode 100644 index 0000000000..5dc132eb2d --- /dev/null +++ b/tests/components/animation/test.esp32.yaml @@ -0,0 +1,23 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 12 + dc_pin: 13 + reset_pin: 21 + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: ../../pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/components/animation/test.esp8266.yaml b/tests/components/animation/test.esp8266.yaml new file mode 100644 index 0000000000..ef0f483a79 --- /dev/null +++ b/tests/components/animation/test.esp8266.yaml @@ -0,0 +1,23 @@ +spi: + - id: spi_main_lcd + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 5 + dc_pin: 15 + reset_pin: 16 + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: ../../pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/components/animation/test.rp2040.yaml b/tests/components/animation/test.rp2040.yaml new file mode 100644 index 0000000000..6ee29a3347 --- /dev/null +++ b/tests/components/animation/test.rp2040.yaml @@ -0,0 +1,23 @@ +spi: + - id: spi_main_lcd + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 20 + dc_pin: 21 + reset_pin: 22 + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: ../../pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/components/anova/test.esp32-c3-idf.yaml b/tests/components/anova/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..c4162fe71e --- /dev/null +++ b/tests/components/anova/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: anova_blec + +climate: + - platform: anova + name: Anova cooker + ble_client_id: anova_blec + unit_of_measurement: c diff --git a/tests/components/anova/test.esp32-c3.yaml b/tests/components/anova/test.esp32-c3.yaml new file mode 100644 index 0000000000..c4162fe71e --- /dev/null +++ b/tests/components/anova/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: anova_blec + +climate: + - platform: anova + name: Anova cooker + ble_client_id: anova_blec + unit_of_measurement: c diff --git a/tests/components/anova/test.esp32-idf.yaml b/tests/components/anova/test.esp32-idf.yaml new file mode 100644 index 0000000000..c4162fe71e --- /dev/null +++ b/tests/components/anova/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: anova_blec + +climate: + - platform: anova + name: Anova cooker + ble_client_id: anova_blec + unit_of_measurement: c diff --git a/tests/components/anova/test.esp32.yaml b/tests/components/anova/test.esp32.yaml new file mode 100644 index 0000000000..c4162fe71e --- /dev/null +++ b/tests/components/anova/test.esp32.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: anova_blec + +climate: + - platform: anova + name: Anova cooker + ble_client_id: anova_blec + unit_of_measurement: c diff --git a/tests/components/apds9960/test.esp32-c3-idf.yaml b/tests/components/apds9960/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..f6b6f7bac0 --- /dev/null +++ b/tests/components/apds9960/test.esp32-c3-idf.yaml @@ -0,0 +1,48 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +apds9960: + address: 0x20 + update_interval: 60s + +binary_sensor: + - platform: apds9960 + id: apds9960_binary_sensor + direction: up + name: APDS9960 Up + device_class: motion + filters: + - invert + - delayed_on: 20ms + - delayed_off: 20ms + - lambda: "return false;" + on_state: + - logger.log: New state + - platform: apds9960 + direction: down + name: APDS9960 Down + - platform: apds9960 + direction: left + name: APDS9960 Left + - platform: apds9960 + direction: right + name: APDS9960 Right + +sensor: + - platform: apds9960 + type: proximity + name: APDS9960 Proximity + - platform: apds9960 + type: clear + name: APDS9960 Clear + - platform: apds9960 + type: red + name: APDS9960 Red + - platform: apds9960 + type: green + name: APDS9960 Green + - platform: apds9960 + type: blue + name: APDS9960 Blue diff --git a/tests/components/apds9960/test.esp32-c3.yaml b/tests/components/apds9960/test.esp32-c3.yaml new file mode 100644 index 0000000000..f6b6f7bac0 --- /dev/null +++ b/tests/components/apds9960/test.esp32-c3.yaml @@ -0,0 +1,48 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +apds9960: + address: 0x20 + update_interval: 60s + +binary_sensor: + - platform: apds9960 + id: apds9960_binary_sensor + direction: up + name: APDS9960 Up + device_class: motion + filters: + - invert + - delayed_on: 20ms + - delayed_off: 20ms + - lambda: "return false;" + on_state: + - logger.log: New state + - platform: apds9960 + direction: down + name: APDS9960 Down + - platform: apds9960 + direction: left + name: APDS9960 Left + - platform: apds9960 + direction: right + name: APDS9960 Right + +sensor: + - platform: apds9960 + type: proximity + name: APDS9960 Proximity + - platform: apds9960 + type: clear + name: APDS9960 Clear + - platform: apds9960 + type: red + name: APDS9960 Red + - platform: apds9960 + type: green + name: APDS9960 Green + - platform: apds9960 + type: blue + name: APDS9960 Blue diff --git a/tests/components/apds9960/test.esp32-idf.yaml b/tests/components/apds9960/test.esp32-idf.yaml new file mode 100644 index 0000000000..7ff70a4d47 --- /dev/null +++ b/tests/components/apds9960/test.esp32-idf.yaml @@ -0,0 +1,48 @@ +i2c: + - id: i2c_bme280 + scl: 16 + sda: 17 + +apds9960: + address: 0x20 + update_interval: 60s + +binary_sensor: + - platform: apds9960 + id: apds9960_binary_sensor + direction: up + name: APDS9960 Up + device_class: motion + filters: + - invert + - delayed_on: 20ms + - delayed_off: 20ms + - lambda: "return false;" + on_state: + - logger.log: New state + - platform: apds9960 + direction: down + name: APDS9960 Down + - platform: apds9960 + direction: left + name: APDS9960 Left + - platform: apds9960 + direction: right + name: APDS9960 Right + +sensor: + - platform: apds9960 + type: proximity + name: APDS9960 Proximity + - platform: apds9960 + type: clear + name: APDS9960 Clear + - platform: apds9960 + type: red + name: APDS9960 Red + - platform: apds9960 + type: green + name: APDS9960 Green + - platform: apds9960 + type: blue + name: APDS9960 Blue diff --git a/tests/components/apds9960/test.esp32.yaml b/tests/components/apds9960/test.esp32.yaml new file mode 100644 index 0000000000..7ff70a4d47 --- /dev/null +++ b/tests/components/apds9960/test.esp32.yaml @@ -0,0 +1,48 @@ +i2c: + - id: i2c_bme280 + scl: 16 + sda: 17 + +apds9960: + address: 0x20 + update_interval: 60s + +binary_sensor: + - platform: apds9960 + id: apds9960_binary_sensor + direction: up + name: APDS9960 Up + device_class: motion + filters: + - invert + - delayed_on: 20ms + - delayed_off: 20ms + - lambda: "return false;" + on_state: + - logger.log: New state + - platform: apds9960 + direction: down + name: APDS9960 Down + - platform: apds9960 + direction: left + name: APDS9960 Left + - platform: apds9960 + direction: right + name: APDS9960 Right + +sensor: + - platform: apds9960 + type: proximity + name: APDS9960 Proximity + - platform: apds9960 + type: clear + name: APDS9960 Clear + - platform: apds9960 + type: red + name: APDS9960 Red + - platform: apds9960 + type: green + name: APDS9960 Green + - platform: apds9960 + type: blue + name: APDS9960 Blue diff --git a/tests/components/apds9960/test.esp8266.yaml b/tests/components/apds9960/test.esp8266.yaml new file mode 100644 index 0000000000..f6b6f7bac0 --- /dev/null +++ b/tests/components/apds9960/test.esp8266.yaml @@ -0,0 +1,48 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +apds9960: + address: 0x20 + update_interval: 60s + +binary_sensor: + - platform: apds9960 + id: apds9960_binary_sensor + direction: up + name: APDS9960 Up + device_class: motion + filters: + - invert + - delayed_on: 20ms + - delayed_off: 20ms + - lambda: "return false;" + on_state: + - logger.log: New state + - platform: apds9960 + direction: down + name: APDS9960 Down + - platform: apds9960 + direction: left + name: APDS9960 Left + - platform: apds9960 + direction: right + name: APDS9960 Right + +sensor: + - platform: apds9960 + type: proximity + name: APDS9960 Proximity + - platform: apds9960 + type: clear + name: APDS9960 Clear + - platform: apds9960 + type: red + name: APDS9960 Red + - platform: apds9960 + type: green + name: APDS9960 Green + - platform: apds9960 + type: blue + name: APDS9960 Blue diff --git a/tests/components/apds9960/test.rp2040.yaml b/tests/components/apds9960/test.rp2040.yaml new file mode 100644 index 0000000000..f6b6f7bac0 --- /dev/null +++ b/tests/components/apds9960/test.rp2040.yaml @@ -0,0 +1,48 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +apds9960: + address: 0x20 + update_interval: 60s + +binary_sensor: + - platform: apds9960 + id: apds9960_binary_sensor + direction: up + name: APDS9960 Up + device_class: motion + filters: + - invert + - delayed_on: 20ms + - delayed_off: 20ms + - lambda: "return false;" + on_state: + - logger.log: New state + - platform: apds9960 + direction: down + name: APDS9960 Down + - platform: apds9960 + direction: left + name: APDS9960 Left + - platform: apds9960 + direction: right + name: APDS9960 Right + +sensor: + - platform: apds9960 + type: proximity + name: APDS9960 Proximity + - platform: apds9960 + type: clear + name: APDS9960 Clear + - platform: apds9960 + type: red + name: APDS9960 Red + - platform: apds9960 + type: green + name: APDS9960 Green + - platform: apds9960 + type: blue + name: APDS9960 Blue diff --git a/tests/components/api/test.esp32-c3-idf.yaml b/tests/components/api/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..3c56811b95 --- /dev/null +++ b/tests/components/api/test.esp32-c3-idf.yaml @@ -0,0 +1,63 @@ +esphome: + on_boot: + then: + - homeassistant.event: + event: esphome.button_pressed + data: + message: Button was pressed + - homeassistant.service: + service: notify.html5 + data: + message: Button was pressed + - homeassistant.tag_scanned: pulse + +wifi: + ssid: MySSID + password: password1 + +api: + port: 8000 + password: pwd + reboot_timeout: 0min + encryption: + key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= + services: + - service: hello_world + variables: + name: string + then: + - logger.log: + format: Hello World %s! + args: + - name.c_str() + - service: empty_service + then: + - logger.log: Service Called + - service: all_types + variables: + bool_: bool + int_: int + float_: float + string_: string + then: + - logger.log: Something happened + - service: array_types + variables: + bool_arr: bool[] + int_arr: int[] + float_arr: float[] + string_arr: string[] + then: + - logger.log: + # yamllint disable rule:line-length + format: "Bool: %s (%u), Int: %d (%u), Float: %f (%u), String: %s (%u)" + # yamllint enable rule:line-length + args: + - YESNO(bool_arr[0]) + - bool_arr.size() + - int_arr[0] + - int_arr.size() + - float_arr[0] + - float_arr.size() + - string_arr[0].c_str() + - string_arr.size() diff --git a/tests/components/api/test.esp32-c3.yaml b/tests/components/api/test.esp32-c3.yaml new file mode 100644 index 0000000000..3c56811b95 --- /dev/null +++ b/tests/components/api/test.esp32-c3.yaml @@ -0,0 +1,63 @@ +esphome: + on_boot: + then: + - homeassistant.event: + event: esphome.button_pressed + data: + message: Button was pressed + - homeassistant.service: + service: notify.html5 + data: + message: Button was pressed + - homeassistant.tag_scanned: pulse + +wifi: + ssid: MySSID + password: password1 + +api: + port: 8000 + password: pwd + reboot_timeout: 0min + encryption: + key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= + services: + - service: hello_world + variables: + name: string + then: + - logger.log: + format: Hello World %s! + args: + - name.c_str() + - service: empty_service + then: + - logger.log: Service Called + - service: all_types + variables: + bool_: bool + int_: int + float_: float + string_: string + then: + - logger.log: Something happened + - service: array_types + variables: + bool_arr: bool[] + int_arr: int[] + float_arr: float[] + string_arr: string[] + then: + - logger.log: + # yamllint disable rule:line-length + format: "Bool: %s (%u), Int: %d (%u), Float: %f (%u), String: %s (%u)" + # yamllint enable rule:line-length + args: + - YESNO(bool_arr[0]) + - bool_arr.size() + - int_arr[0] + - int_arr.size() + - float_arr[0] + - float_arr.size() + - string_arr[0].c_str() + - string_arr.size() diff --git a/tests/components/api/test.esp32-idf.yaml b/tests/components/api/test.esp32-idf.yaml new file mode 100644 index 0000000000..3c56811b95 --- /dev/null +++ b/tests/components/api/test.esp32-idf.yaml @@ -0,0 +1,63 @@ +esphome: + on_boot: + then: + - homeassistant.event: + event: esphome.button_pressed + data: + message: Button was pressed + - homeassistant.service: + service: notify.html5 + data: + message: Button was pressed + - homeassistant.tag_scanned: pulse + +wifi: + ssid: MySSID + password: password1 + +api: + port: 8000 + password: pwd + reboot_timeout: 0min + encryption: + key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= + services: + - service: hello_world + variables: + name: string + then: + - logger.log: + format: Hello World %s! + args: + - name.c_str() + - service: empty_service + then: + - logger.log: Service Called + - service: all_types + variables: + bool_: bool + int_: int + float_: float + string_: string + then: + - logger.log: Something happened + - service: array_types + variables: + bool_arr: bool[] + int_arr: int[] + float_arr: float[] + string_arr: string[] + then: + - logger.log: + # yamllint disable rule:line-length + format: "Bool: %s (%u), Int: %d (%u), Float: %f (%u), String: %s (%u)" + # yamllint enable rule:line-length + args: + - YESNO(bool_arr[0]) + - bool_arr.size() + - int_arr[0] + - int_arr.size() + - float_arr[0] + - float_arr.size() + - string_arr[0].c_str() + - string_arr.size() diff --git a/tests/components/api/test.esp32.yaml b/tests/components/api/test.esp32.yaml new file mode 100644 index 0000000000..3c56811b95 --- /dev/null +++ b/tests/components/api/test.esp32.yaml @@ -0,0 +1,63 @@ +esphome: + on_boot: + then: + - homeassistant.event: + event: esphome.button_pressed + data: + message: Button was pressed + - homeassistant.service: + service: notify.html5 + data: + message: Button was pressed + - homeassistant.tag_scanned: pulse + +wifi: + ssid: MySSID + password: password1 + +api: + port: 8000 + password: pwd + reboot_timeout: 0min + encryption: + key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= + services: + - service: hello_world + variables: + name: string + then: + - logger.log: + format: Hello World %s! + args: + - name.c_str() + - service: empty_service + then: + - logger.log: Service Called + - service: all_types + variables: + bool_: bool + int_: int + float_: float + string_: string + then: + - logger.log: Something happened + - service: array_types + variables: + bool_arr: bool[] + int_arr: int[] + float_arr: float[] + string_arr: string[] + then: + - logger.log: + # yamllint disable rule:line-length + format: "Bool: %s (%u), Int: %d (%u), Float: %f (%u), String: %s (%u)" + # yamllint enable rule:line-length + args: + - YESNO(bool_arr[0]) + - bool_arr.size() + - int_arr[0] + - int_arr.size() + - float_arr[0] + - float_arr.size() + - string_arr[0].c_str() + - string_arr.size() diff --git a/tests/components/api/test.esp8266.yaml b/tests/components/api/test.esp8266.yaml new file mode 100644 index 0000000000..3c56811b95 --- /dev/null +++ b/tests/components/api/test.esp8266.yaml @@ -0,0 +1,63 @@ +esphome: + on_boot: + then: + - homeassistant.event: + event: esphome.button_pressed + data: + message: Button was pressed + - homeassistant.service: + service: notify.html5 + data: + message: Button was pressed + - homeassistant.tag_scanned: pulse + +wifi: + ssid: MySSID + password: password1 + +api: + port: 8000 + password: pwd + reboot_timeout: 0min + encryption: + key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= + services: + - service: hello_world + variables: + name: string + then: + - logger.log: + format: Hello World %s! + args: + - name.c_str() + - service: empty_service + then: + - logger.log: Service Called + - service: all_types + variables: + bool_: bool + int_: int + float_: float + string_: string + then: + - logger.log: Something happened + - service: array_types + variables: + bool_arr: bool[] + int_arr: int[] + float_arr: float[] + string_arr: string[] + then: + - logger.log: + # yamllint disable rule:line-length + format: "Bool: %s (%u), Int: %d (%u), Float: %f (%u), String: %s (%u)" + # yamllint enable rule:line-length + args: + - YESNO(bool_arr[0]) + - bool_arr.size() + - int_arr[0] + - int_arr.size() + - float_arr[0] + - float_arr.size() + - string_arr[0].c_str() + - string_arr.size() diff --git a/tests/components/api/test.rp2040.yaml b/tests/components/api/test.rp2040.yaml new file mode 100644 index 0000000000..3c56811b95 --- /dev/null +++ b/tests/components/api/test.rp2040.yaml @@ -0,0 +1,63 @@ +esphome: + on_boot: + then: + - homeassistant.event: + event: esphome.button_pressed + data: + message: Button was pressed + - homeassistant.service: + service: notify.html5 + data: + message: Button was pressed + - homeassistant.tag_scanned: pulse + +wifi: + ssid: MySSID + password: password1 + +api: + port: 8000 + password: pwd + reboot_timeout: 0min + encryption: + key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= + services: + - service: hello_world + variables: + name: string + then: + - logger.log: + format: Hello World %s! + args: + - name.c_str() + - service: empty_service + then: + - logger.log: Service Called + - service: all_types + variables: + bool_: bool + int_: int + float_: float + string_: string + then: + - logger.log: Something happened + - service: array_types + variables: + bool_arr: bool[] + int_arr: int[] + float_arr: float[] + string_arr: string[] + then: + - logger.log: + # yamllint disable rule:line-length + format: "Bool: %s (%u), Int: %d (%u), Float: %f (%u), String: %s (%u)" + # yamllint enable rule:line-length + args: + - YESNO(bool_arr[0]) + - bool_arr.size() + - int_arr[0] + - int_arr.size() + - float_arr[0] + - float_arr.size() + - string_arr[0].c_str() + - string_arr.size() diff --git a/tests/components/as3935_i2c/test.esp32-c3-idf.yaml b/tests/components/as3935_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..c72556dbac --- /dev/null +++ b/tests/components/as3935_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_as3935 + scl: 5 + sda: 4 + +as3935_i2c: + irq_pin: 6 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_i2c/test.esp32-c3.yaml b/tests/components/as3935_i2c/test.esp32-c3.yaml new file mode 100644 index 0000000000..c72556dbac --- /dev/null +++ b/tests/components/as3935_i2c/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_as3935 + scl: 5 + sda: 4 + +as3935_i2c: + irq_pin: 6 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_i2c/test.esp32-idf.yaml b/tests/components/as3935_i2c/test.esp32-idf.yaml new file mode 100644 index 0000000000..fad703bee5 --- /dev/null +++ b/tests/components/as3935_i2c/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_as3935 + scl: 16 + sda: 17 + +as3935_i2c: + irq_pin: 12 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_i2c/test.esp32.yaml b/tests/components/as3935_i2c/test.esp32.yaml new file mode 100644 index 0000000000..fad703bee5 --- /dev/null +++ b/tests/components/as3935_i2c/test.esp32.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_as3935 + scl: 16 + sda: 17 + +as3935_i2c: + irq_pin: 12 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_i2c/test.esp8266.yaml b/tests/components/as3935_i2c/test.esp8266.yaml new file mode 100644 index 0000000000..adba9e440f --- /dev/null +++ b/tests/components/as3935_i2c/test.esp8266.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_as3935 + scl: 5 + sda: 4 + +as3935_i2c: + irq_pin: 15 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_i2c/test.rp2040.yaml b/tests/components/as3935_i2c/test.rp2040.yaml new file mode 100644 index 0000000000..c72556dbac --- /dev/null +++ b/tests/components/as3935_i2c/test.rp2040.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_as3935 + scl: 5 + sda: 4 + +as3935_i2c: + irq_pin: 6 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_spi/test.esp32-c3-idf.yaml b/tests/components/as3935_spi/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..7a4a01aeea --- /dev/null +++ b/tests/components/as3935_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +spi: + - id: spi_as3935 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +as3935_spi: + cs_pin: 2 + irq_pin: 3 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_spi/test.esp32-c3.yaml b/tests/components/as3935_spi/test.esp32-c3.yaml new file mode 100644 index 0000000000..7a4a01aeea --- /dev/null +++ b/tests/components/as3935_spi/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +spi: + - id: spi_as3935 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +as3935_spi: + cs_pin: 2 + irq_pin: 3 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_spi/test.esp32-idf.yaml b/tests/components/as3935_spi/test.esp32-idf.yaml new file mode 100644 index 0000000000..813a39cb23 --- /dev/null +++ b/tests/components/as3935_spi/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +spi: + - id: spi_as3935 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +as3935_spi: + cs_pin: 12 + irq_pin: 13 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_spi/test.esp32.yaml b/tests/components/as3935_spi/test.esp32.yaml new file mode 100644 index 0000000000..813a39cb23 --- /dev/null +++ b/tests/components/as3935_spi/test.esp32.yaml @@ -0,0 +1,20 @@ +spi: + - id: spi_as3935 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +as3935_spi: + cs_pin: 12 + irq_pin: 13 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_spi/test.esp8266.yaml b/tests/components/as3935_spi/test.esp8266.yaml new file mode 100644 index 0000000000..38a40b0833 --- /dev/null +++ b/tests/components/as3935_spi/test.esp8266.yaml @@ -0,0 +1,20 @@ +spi: + - id: spi_as3935 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +as3935_spi: + cs_pin: 15 + irq_pin: 16 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_spi/test.rp2040.yaml b/tests/components/as3935_spi/test.rp2040.yaml new file mode 100644 index 0000000000..528759d97d --- /dev/null +++ b/tests/components/as3935_spi/test.rp2040.yaml @@ -0,0 +1,20 @@ +spi: + - id: spi_as3935 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +as3935_spi: + cs_pin: 6 + irq_pin: 7 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as5600/test.esp32-c3-idf.yaml b/tests/components/as5600/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..e074fa5e0c --- /dev/null +++ b/tests/components/as5600/test.esp32-c3-idf.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +as5600: + dir_pin: 6 + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 + +sensor: + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status diff --git a/tests/components/as5600/test.esp32-c3.yaml b/tests/components/as5600/test.esp32-c3.yaml new file mode 100644 index 0000000000..e074fa5e0c --- /dev/null +++ b/tests/components/as5600/test.esp32-c3.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +as5600: + dir_pin: 6 + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 + +sensor: + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status diff --git a/tests/components/as5600/test.esp32-idf.yaml b/tests/components/as5600/test.esp32-idf.yaml new file mode 100644 index 0000000000..312ee9ad04 --- /dev/null +++ b/tests/components/as5600/test.esp32-idf.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_as5600 + scl: 16 + sda: 17 + +as5600: + dir_pin: 12 + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 + +sensor: + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status diff --git a/tests/components/as5600/test.esp32.yaml b/tests/components/as5600/test.esp32.yaml new file mode 100644 index 0000000000..312ee9ad04 --- /dev/null +++ b/tests/components/as5600/test.esp32.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_as5600 + scl: 16 + sda: 17 + +as5600: + dir_pin: 12 + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 + +sensor: + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status diff --git a/tests/components/as5600/test.esp8266.yaml b/tests/components/as5600/test.esp8266.yaml new file mode 100644 index 0000000000..a232d27305 --- /dev/null +++ b/tests/components/as5600/test.esp8266.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +as5600: + dir_pin: 15 + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 + +sensor: + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status diff --git a/tests/components/as5600/test.rp2040.yaml b/tests/components/as5600/test.rp2040.yaml new file mode 100644 index 0000000000..e074fa5e0c --- /dev/null +++ b/tests/components/as5600/test.rp2040.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +as5600: + dir_pin: 6 + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 + +sensor: + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status diff --git a/tests/components/as7341/test.esp32-c3-idf.yaml b/tests/components/as7341/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..19965d1715 --- /dev/null +++ b/tests/components/as7341/test.esp32-c3-idf.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +sensor: + - platform: as7341 + update_interval: 15s + gain: X8 + atime: 120 + astep: 99 + f1: + name: F1 + f2: + name: F2 + f3: + name: F3 + f4: + name: F4 + f5: + name: F5 + f6: + name: F6 + f7: + name: F7 + f8: + name: F8 + clear: + name: Clear + nir: + name: NIR diff --git a/tests/components/as7341/test.esp32-c3.yaml b/tests/components/as7341/test.esp32-c3.yaml new file mode 100644 index 0000000000..19965d1715 --- /dev/null +++ b/tests/components/as7341/test.esp32-c3.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +sensor: + - platform: as7341 + update_interval: 15s + gain: X8 + atime: 120 + astep: 99 + f1: + name: F1 + f2: + name: F2 + f3: + name: F3 + f4: + name: F4 + f5: + name: F5 + f6: + name: F6 + f7: + name: F7 + f8: + name: F8 + clear: + name: Clear + nir: + name: NIR diff --git a/tests/components/as7341/test.esp32-idf.yaml b/tests/components/as7341/test.esp32-idf.yaml new file mode 100644 index 0000000000..d582a367ac --- /dev/null +++ b/tests/components/as7341/test.esp32-idf.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_as5600 + scl: 16 + sda: 17 + +sensor: + - platform: as7341 + update_interval: 15s + gain: X8 + atime: 120 + astep: 99 + f1: + name: F1 + f2: + name: F2 + f3: + name: F3 + f4: + name: F4 + f5: + name: F5 + f6: + name: F6 + f7: + name: F7 + f8: + name: F8 + clear: + name: Clear + nir: + name: NIR diff --git a/tests/components/as7341/test.esp32.yaml b/tests/components/as7341/test.esp32.yaml new file mode 100644 index 0000000000..d582a367ac --- /dev/null +++ b/tests/components/as7341/test.esp32.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_as5600 + scl: 16 + sda: 17 + +sensor: + - platform: as7341 + update_interval: 15s + gain: X8 + atime: 120 + astep: 99 + f1: + name: F1 + f2: + name: F2 + f3: + name: F3 + f4: + name: F4 + f5: + name: F5 + f6: + name: F6 + f7: + name: F7 + f8: + name: F8 + clear: + name: Clear + nir: + name: NIR diff --git a/tests/components/as7341/test.esp8266.yaml b/tests/components/as7341/test.esp8266.yaml new file mode 100644 index 0000000000..19965d1715 --- /dev/null +++ b/tests/components/as7341/test.esp8266.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +sensor: + - platform: as7341 + update_interval: 15s + gain: X8 + atime: 120 + astep: 99 + f1: + name: F1 + f2: + name: F2 + f3: + name: F3 + f4: + name: F4 + f5: + name: F5 + f6: + name: F6 + f7: + name: F7 + f8: + name: F8 + clear: + name: Clear + nir: + name: NIR diff --git a/tests/components/as7341/test.rp2040.yaml b/tests/components/as7341/test.rp2040.yaml new file mode 100644 index 0000000000..19965d1715 --- /dev/null +++ b/tests/components/as7341/test.rp2040.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +sensor: + - platform: as7341 + update_interval: 15s + gain: X8 + atime: 120 + astep: 99 + f1: + name: F1 + f2: + name: F2 + f3: + name: F3 + f4: + name: F4 + f5: + name: F5 + f6: + name: F6 + f7: + name: F7 + f8: + name: F8 + clear: + name: Clear + nir: + name: NIR diff --git a/tests/components/at581x/test.esp32-c3-idf.yaml b/tests/components/at581x/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..b49a283eca --- /dev/null +++ b/tests/components/at581x/test.esp32-c3-idf.yaml @@ -0,0 +1,38 @@ +esphome: + on_boot: + then: + - at581x.settings: + id: "Waveradar" + hw_frontend_reset: false + frequency: 5800MHz + sensing_distance: 200 + poweron_selfcheck_time: 2s + protect_time: 1s + trigger_base: 500ms + trigger_keep: 10s + stage_gain: 3 + power_consumption: 70uA + - at581x.reset: + id: "Waveradar" + +at581x: + id: "Waveradar" + i2c_id: i2c_bus + +i2c: + sda: 8 + scl: 9 + scan: true + frequency: 100kHz + setup_priority: -100 + id: i2c_bus + +binary_sensor: + - platform: gpio + pin: GPIO21 + name: "Radar motion" + +switch: + - platform: at581x + at581x_id: "Waveradar" + name: "Enable Radar" diff --git a/tests/components/at581x/test.esp32-c3.yaml b/tests/components/at581x/test.esp32-c3.yaml new file mode 100644 index 0000000000..b49a283eca --- /dev/null +++ b/tests/components/at581x/test.esp32-c3.yaml @@ -0,0 +1,38 @@ +esphome: + on_boot: + then: + - at581x.settings: + id: "Waveradar" + hw_frontend_reset: false + frequency: 5800MHz + sensing_distance: 200 + poweron_selfcheck_time: 2s + protect_time: 1s + trigger_base: 500ms + trigger_keep: 10s + stage_gain: 3 + power_consumption: 70uA + - at581x.reset: + id: "Waveradar" + +at581x: + id: "Waveradar" + i2c_id: i2c_bus + +i2c: + sda: 8 + scl: 9 + scan: true + frequency: 100kHz + setup_priority: -100 + id: i2c_bus + +binary_sensor: + - platform: gpio + pin: GPIO21 + name: "Radar motion" + +switch: + - platform: at581x + at581x_id: "Waveradar" + name: "Enable Radar" diff --git a/tests/components/at581x/test.esp32-idf.yaml b/tests/components/at581x/test.esp32-idf.yaml new file mode 100644 index 0000000000..ff84e61e1e --- /dev/null +++ b/tests/components/at581x/test.esp32-idf.yaml @@ -0,0 +1,38 @@ +esphome: + on_boot: + then: + - at581x.settings: + id: "Waveradar" + hw_frontend_reset: false + frequency: 5800MHz + sensing_distance: 200 + poweron_selfcheck_time: 2s + protect_time: 1s + trigger_base: 500ms + trigger_keep: 10s + stage_gain: 3 + power_consumption: 70uA + - at581x.reset: + id: "Waveradar" + +at581x: + id: "Waveradar" + i2c_id: i2c_bus + +i2c: + sda: 14 + scl: 15 + scan: true + frequency: 100kHz + setup_priority: -100 + id: i2c_bus + +binary_sensor: + - platform: gpio + pin: GPIO21 + name: "Radar motion" + +switch: + - platform: at581x + at581x_id: "Waveradar" + name: "Enable Radar" diff --git a/tests/components/at581x/test.esp32.yaml b/tests/components/at581x/test.esp32.yaml new file mode 100644 index 0000000000..ff84e61e1e --- /dev/null +++ b/tests/components/at581x/test.esp32.yaml @@ -0,0 +1,38 @@ +esphome: + on_boot: + then: + - at581x.settings: + id: "Waveradar" + hw_frontend_reset: false + frequency: 5800MHz + sensing_distance: 200 + poweron_selfcheck_time: 2s + protect_time: 1s + trigger_base: 500ms + trigger_keep: 10s + stage_gain: 3 + power_consumption: 70uA + - at581x.reset: + id: "Waveradar" + +at581x: + id: "Waveradar" + i2c_id: i2c_bus + +i2c: + sda: 14 + scl: 15 + scan: true + frequency: 100kHz + setup_priority: -100 + id: i2c_bus + +binary_sensor: + - platform: gpio + pin: GPIO21 + name: "Radar motion" + +switch: + - platform: at581x + at581x_id: "Waveradar" + name: "Enable Radar" diff --git a/tests/components/at581x/test.esp8266.yaml b/tests/components/at581x/test.esp8266.yaml new file mode 100644 index 0000000000..a7b0069045 --- /dev/null +++ b/tests/components/at581x/test.esp8266.yaml @@ -0,0 +1,38 @@ +esphome: + on_boot: + then: + - at581x.settings: + id: "Waveradar" + hw_frontend_reset: false + frequency: 5800MHz + sensing_distance: 200 + poweron_selfcheck_time: 2s + protect_time: 1s + trigger_base: 500ms + trigger_keep: 10s + stage_gain: 3 + power_consumption: 70uA + - at581x.reset: + id: "Waveradar" + +at581x: + id: "Waveradar" + i2c_id: i2c_bus + +i2c: + sda: 14 + scl: 15 + scan: true + frequency: 100kHz + setup_priority: -100 + id: i2c_bus + +binary_sensor: + - platform: gpio + pin: GPIO4 + name: "Radar motion" + +switch: + - platform: at581x + at581x_id: "Waveradar" + name: "Enable Radar" diff --git a/tests/components/at581x/test.rp2040.yaml b/tests/components/at581x/test.rp2040.yaml new file mode 100644 index 0000000000..b49a283eca --- /dev/null +++ b/tests/components/at581x/test.rp2040.yaml @@ -0,0 +1,38 @@ +esphome: + on_boot: + then: + - at581x.settings: + id: "Waveradar" + hw_frontend_reset: false + frequency: 5800MHz + sensing_distance: 200 + poweron_selfcheck_time: 2s + protect_time: 1s + trigger_base: 500ms + trigger_keep: 10s + stage_gain: 3 + power_consumption: 70uA + - at581x.reset: + id: "Waveradar" + +at581x: + id: "Waveradar" + i2c_id: i2c_bus + +i2c: + sda: 8 + scl: 9 + scan: true + frequency: 100kHz + setup_priority: -100 + id: i2c_bus + +binary_sensor: + - platform: gpio + pin: GPIO21 + name: "Radar motion" + +switch: + - platform: at581x + at581x_id: "Waveradar" + name: "Enable Radar" diff --git a/tests/components/atc_mithermometer/test.esp32-c3-idf.yaml b/tests/components/atc_mithermometer/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..0248090c23 --- /dev/null +++ b/tests/components/atc_mithermometer/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: atc_mithermometer + mac_address: A4:C1:38:4E:16:78 + temperature: + name: ATC Temperature + humidity: + name: ATC Humidity + battery_level: + name: ATC Battery-Level + battery_voltage: + name: ATC Battery-Voltage diff --git a/tests/components/atc_mithermometer/test.esp32-c3.yaml b/tests/components/atc_mithermometer/test.esp32-c3.yaml new file mode 100644 index 0000000000..0248090c23 --- /dev/null +++ b/tests/components/atc_mithermometer/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: atc_mithermometer + mac_address: A4:C1:38:4E:16:78 + temperature: + name: ATC Temperature + humidity: + name: ATC Humidity + battery_level: + name: ATC Battery-Level + battery_voltage: + name: ATC Battery-Voltage diff --git a/tests/components/atc_mithermometer/test.esp32-idf.yaml b/tests/components/atc_mithermometer/test.esp32-idf.yaml new file mode 100644 index 0000000000..0248090c23 --- /dev/null +++ b/tests/components/atc_mithermometer/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: atc_mithermometer + mac_address: A4:C1:38:4E:16:78 + temperature: + name: ATC Temperature + humidity: + name: ATC Humidity + battery_level: + name: ATC Battery-Level + battery_voltage: + name: ATC Battery-Voltage diff --git a/tests/components/atc_mithermometer/test.esp32.yaml b/tests/components/atc_mithermometer/test.esp32.yaml new file mode 100644 index 0000000000..0248090c23 --- /dev/null +++ b/tests/components/atc_mithermometer/test.esp32.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: atc_mithermometer + mac_address: A4:C1:38:4E:16:78 + temperature: + name: ATC Temperature + humidity: + name: ATC Humidity + battery_level: + name: ATC Battery-Level + battery_voltage: + name: ATC Battery-Voltage diff --git a/tests/components/atm90e26/test.esp32-c3-idf.yaml b/tests/components/atm90e26/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ce123bcf72 --- /dev/null +++ b/tests/components/atm90e26/test.esp32-c3-idf.yaml @@ -0,0 +1,26 @@ +spi: + - id: spi_atm90e26 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: atm90e26 + cs_pin: 8 + voltage: + name: Line Voltage + current: + name: CT Amps + power: + name: Active Watts + power_factor: + name: Power Factor + frequency: + name: Line Frequency + line_frequency: 50Hz + meter_constant: 1000 + pl_const: 1429876 + gain_pga: 1X + gain_metering: 7481 + gain_voltage: 26400 + gain_ct: 31251 diff --git a/tests/components/atm90e26/test.esp32-c3.yaml b/tests/components/atm90e26/test.esp32-c3.yaml new file mode 100644 index 0000000000..ce123bcf72 --- /dev/null +++ b/tests/components/atm90e26/test.esp32-c3.yaml @@ -0,0 +1,26 @@ +spi: + - id: spi_atm90e26 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: atm90e26 + cs_pin: 8 + voltage: + name: Line Voltage + current: + name: CT Amps + power: + name: Active Watts + power_factor: + name: Power Factor + frequency: + name: Line Frequency + line_frequency: 50Hz + meter_constant: 1000 + pl_const: 1429876 + gain_pga: 1X + gain_metering: 7481 + gain_voltage: 26400 + gain_ct: 31251 diff --git a/tests/components/atm90e26/test.esp32-idf.yaml b/tests/components/atm90e26/test.esp32-idf.yaml new file mode 100644 index 0000000000..72fb3e5b24 --- /dev/null +++ b/tests/components/atm90e26/test.esp32-idf.yaml @@ -0,0 +1,26 @@ +spi: + - id: spi_atm90e26 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: atm90e26 + cs_pin: 13 + voltage: + name: Line Voltage + current: + name: CT Amps + power: + name: Active Watts + power_factor: + name: Power Factor + frequency: + name: Line Frequency + line_frequency: 50Hz + meter_constant: 1000 + pl_const: 1429876 + gain_pga: 1X + gain_metering: 7481 + gain_voltage: 26400 + gain_ct: 31251 diff --git a/tests/components/atm90e26/test.esp32.yaml b/tests/components/atm90e26/test.esp32.yaml new file mode 100644 index 0000000000..72fb3e5b24 --- /dev/null +++ b/tests/components/atm90e26/test.esp32.yaml @@ -0,0 +1,26 @@ +spi: + - id: spi_atm90e26 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: atm90e26 + cs_pin: 13 + voltage: + name: Line Voltage + current: + name: CT Amps + power: + name: Active Watts + power_factor: + name: Power Factor + frequency: + name: Line Frequency + line_frequency: 50Hz + meter_constant: 1000 + pl_const: 1429876 + gain_pga: 1X + gain_metering: 7481 + gain_voltage: 26400 + gain_ct: 31251 diff --git a/tests/components/atm90e26/test.esp8266.yaml b/tests/components/atm90e26/test.esp8266.yaml new file mode 100644 index 0000000000..68d63cc278 --- /dev/null +++ b/tests/components/atm90e26/test.esp8266.yaml @@ -0,0 +1,26 @@ +spi: + - id: spi_atm90e26 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: atm90e26 + cs_pin: 5 + voltage: + name: Line Voltage + current: + name: CT Amps + power: + name: Active Watts + power_factor: + name: Power Factor + frequency: + name: Line Frequency + line_frequency: 50Hz + meter_constant: 1000 + pl_const: 1429876 + gain_pga: 1X + gain_metering: 7481 + gain_voltage: 26400 + gain_ct: 31251 diff --git a/tests/components/atm90e26/test.rp2040.yaml b/tests/components/atm90e26/test.rp2040.yaml new file mode 100644 index 0000000000..f43277dbb1 --- /dev/null +++ b/tests/components/atm90e26/test.rp2040.yaml @@ -0,0 +1,26 @@ +spi: + - id: spi_atm90e26 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: atm90e26 + cs_pin: 5 + voltage: + name: Line Voltage + current: + name: CT Amps + power: + name: Active Watts + power_factor: + name: Power Factor + frequency: + name: Line Frequency + line_frequency: 50Hz + meter_constant: 1000 + pl_const: 1429876 + gain_pga: 1X + gain_metering: 7481 + gain_voltage: 26400 + gain_ct: 31251 diff --git a/tests/components/atm90e32/test.esp32-c3-idf.yaml b/tests/components/atm90e32/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..263fb6d24e --- /dev/null +++ b/tests/components/atm90e32/test.esp32-c3-idf.yaml @@ -0,0 +1,51 @@ +spi: + - id: spi_atm90e32 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: atm90e32 + cs_pin: 8 + phase_a: + voltage: + name: EMON Line Voltage A + current: + name: EMON CT1 Current + power: + name: EMON Active Power CT1 + reactive_power: + name: EMON Reactive Power CT1 + power_factor: + name: EMON Power Factor CT1 + gain_voltage: 7305 + gain_ct: 27961 + phase_b: + current: + name: EMON CT2 Current + power: + name: EMON Active Power CT2 + reactive_power: + name: EMON Reactive Power CT2 + power_factor: + name: EMON Power Factor CT2 + gain_voltage: 7305 + gain_ct: 27961 + phase_c: + current: + name: EMON CT3 Current + power: + name: EMON Active Power CT3 + reactive_power: + name: EMON Reactive Power CT3 + power_factor: + name: EMON Power Factor CT3 + gain_voltage: 7305 + gain_ct: 27961 + frequency: + name: EMON Line Frequency + chip_temperature: + name: EMON Chip Temp A + line_frequency: 60Hz + current_phases: 3 + gain_pga: 2X diff --git a/tests/components/atm90e32/test.esp32-c3.yaml b/tests/components/atm90e32/test.esp32-c3.yaml new file mode 100644 index 0000000000..263fb6d24e --- /dev/null +++ b/tests/components/atm90e32/test.esp32-c3.yaml @@ -0,0 +1,51 @@ +spi: + - id: spi_atm90e32 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: atm90e32 + cs_pin: 8 + phase_a: + voltage: + name: EMON Line Voltage A + current: + name: EMON CT1 Current + power: + name: EMON Active Power CT1 + reactive_power: + name: EMON Reactive Power CT1 + power_factor: + name: EMON Power Factor CT1 + gain_voltage: 7305 + gain_ct: 27961 + phase_b: + current: + name: EMON CT2 Current + power: + name: EMON Active Power CT2 + reactive_power: + name: EMON Reactive Power CT2 + power_factor: + name: EMON Power Factor CT2 + gain_voltage: 7305 + gain_ct: 27961 + phase_c: + current: + name: EMON CT3 Current + power: + name: EMON Active Power CT3 + reactive_power: + name: EMON Reactive Power CT3 + power_factor: + name: EMON Power Factor CT3 + gain_voltage: 7305 + gain_ct: 27961 + frequency: + name: EMON Line Frequency + chip_temperature: + name: EMON Chip Temp A + line_frequency: 60Hz + current_phases: 3 + gain_pga: 2X diff --git a/tests/components/atm90e32/test.esp32-idf.yaml b/tests/components/atm90e32/test.esp32-idf.yaml new file mode 100644 index 0000000000..131270f8ad --- /dev/null +++ b/tests/components/atm90e32/test.esp32-idf.yaml @@ -0,0 +1,51 @@ +spi: + - id: spi_atm90e32 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: atm90e32 + cs_pin: 13 + phase_a: + voltage: + name: EMON Line Voltage A + current: + name: EMON CT1 Current + power: + name: EMON Active Power CT1 + reactive_power: + name: EMON Reactive Power CT1 + power_factor: + name: EMON Power Factor CT1 + gain_voltage: 7305 + gain_ct: 27961 + phase_b: + current: + name: EMON CT2 Current + power: + name: EMON Active Power CT2 + reactive_power: + name: EMON Reactive Power CT2 + power_factor: + name: EMON Power Factor CT2 + gain_voltage: 7305 + gain_ct: 27961 + phase_c: + current: + name: EMON CT3 Current + power: + name: EMON Active Power CT3 + reactive_power: + name: EMON Reactive Power CT3 + power_factor: + name: EMON Power Factor CT3 + gain_voltage: 7305 + gain_ct: 27961 + frequency: + name: EMON Line Frequency + chip_temperature: + name: EMON Chip Temp A + line_frequency: 60Hz + current_phases: 3 + gain_pga: 2X diff --git a/tests/components/atm90e32/test.esp32.yaml b/tests/components/atm90e32/test.esp32.yaml new file mode 100644 index 0000000000..131270f8ad --- /dev/null +++ b/tests/components/atm90e32/test.esp32.yaml @@ -0,0 +1,51 @@ +spi: + - id: spi_atm90e32 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: atm90e32 + cs_pin: 13 + phase_a: + voltage: + name: EMON Line Voltage A + current: + name: EMON CT1 Current + power: + name: EMON Active Power CT1 + reactive_power: + name: EMON Reactive Power CT1 + power_factor: + name: EMON Power Factor CT1 + gain_voltage: 7305 + gain_ct: 27961 + phase_b: + current: + name: EMON CT2 Current + power: + name: EMON Active Power CT2 + reactive_power: + name: EMON Reactive Power CT2 + power_factor: + name: EMON Power Factor CT2 + gain_voltage: 7305 + gain_ct: 27961 + phase_c: + current: + name: EMON CT3 Current + power: + name: EMON Active Power CT3 + reactive_power: + name: EMON Reactive Power CT3 + power_factor: + name: EMON Power Factor CT3 + gain_voltage: 7305 + gain_ct: 27961 + frequency: + name: EMON Line Frequency + chip_temperature: + name: EMON Chip Temp A + line_frequency: 60Hz + current_phases: 3 + gain_pga: 2X diff --git a/tests/components/atm90e32/test.esp8266.yaml b/tests/components/atm90e32/test.esp8266.yaml new file mode 100644 index 0000000000..e8e2abc1a9 --- /dev/null +++ b/tests/components/atm90e32/test.esp8266.yaml @@ -0,0 +1,51 @@ +spi: + - id: spi_atm90e32 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: atm90e32 + cs_pin: 5 + phase_a: + voltage: + name: EMON Line Voltage A + current: + name: EMON CT1 Current + power: + name: EMON Active Power CT1 + reactive_power: + name: EMON Reactive Power CT1 + power_factor: + name: EMON Power Factor CT1 + gain_voltage: 7305 + gain_ct: 27961 + phase_b: + current: + name: EMON CT2 Current + power: + name: EMON Active Power CT2 + reactive_power: + name: EMON Reactive Power CT2 + power_factor: + name: EMON Power Factor CT2 + gain_voltage: 7305 + gain_ct: 27961 + phase_c: + current: + name: EMON CT3 Current + power: + name: EMON Active Power CT3 + reactive_power: + name: EMON Reactive Power CT3 + power_factor: + name: EMON Power Factor CT3 + gain_voltage: 7305 + gain_ct: 27961 + frequency: + name: EMON Line Frequency + chip_temperature: + name: EMON Chip Temp A + line_frequency: 60Hz + current_phases: 3 + gain_pga: 2X diff --git a/tests/components/atm90e32/test.rp2040.yaml b/tests/components/atm90e32/test.rp2040.yaml new file mode 100644 index 0000000000..525e0b801a --- /dev/null +++ b/tests/components/atm90e32/test.rp2040.yaml @@ -0,0 +1,51 @@ +spi: + - id: spi_atm90e32 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: atm90e32 + cs_pin: 5 + phase_a: + voltage: + name: EMON Line Voltage A + current: + name: EMON CT1 Current + power: + name: EMON Active Power CT1 + reactive_power: + name: EMON Reactive Power CT1 + power_factor: + name: EMON Power Factor CT1 + gain_voltage: 7305 + gain_ct: 27961 + phase_b: + current: + name: EMON CT2 Current + power: + name: EMON Active Power CT2 + reactive_power: + name: EMON Reactive Power CT2 + power_factor: + name: EMON Power Factor CT2 + gain_voltage: 7305 + gain_ct: 27961 + phase_c: + current: + name: EMON CT3 Current + power: + name: EMON Active Power CT3 + reactive_power: + name: EMON Reactive Power CT3 + power_factor: + name: EMON Power Factor CT3 + gain_voltage: 7305 + gain_ct: 27961 + frequency: + name: EMON Line Frequency + chip_temperature: + name: EMON Chip Temp A + line_frequency: 60Hz + current_phases: 3 + gain_pga: 2X diff --git a/tests/components/b_parasite/test.esp32-c3-idf.yaml b/tests/components/b_parasite/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..262e891bb2 --- /dev/null +++ b/tests/components/b_parasite/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + - platform: b_parasite + mac_address: F0:CA:F0:CA:01:01 + humidity: + name: b-parasite Air Humidity + temperature: + name: b-parasite Air Temperature + moisture: + name: b-parasite Soil Moisture + battery_voltage: + name: b-parasite Battery Voltage + illuminance: + name: b-parasite Illuminance diff --git a/tests/components/b_parasite/test.esp32-c3.yaml b/tests/components/b_parasite/test.esp32-c3.yaml new file mode 100644 index 0000000000..262e891bb2 --- /dev/null +++ b/tests/components/b_parasite/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + - platform: b_parasite + mac_address: F0:CA:F0:CA:01:01 + humidity: + name: b-parasite Air Humidity + temperature: + name: b-parasite Air Temperature + moisture: + name: b-parasite Soil Moisture + battery_voltage: + name: b-parasite Battery Voltage + illuminance: + name: b-parasite Illuminance diff --git a/tests/components/b_parasite/test.esp32-idf.yaml b/tests/components/b_parasite/test.esp32-idf.yaml new file mode 100644 index 0000000000..262e891bb2 --- /dev/null +++ b/tests/components/b_parasite/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + - platform: b_parasite + mac_address: F0:CA:F0:CA:01:01 + humidity: + name: b-parasite Air Humidity + temperature: + name: b-parasite Air Temperature + moisture: + name: b-parasite Soil Moisture + battery_voltage: + name: b-parasite Battery Voltage + illuminance: + name: b-parasite Illuminance diff --git a/tests/components/b_parasite/test.esp32.yaml b/tests/components/b_parasite/test.esp32.yaml new file mode 100644 index 0000000000..262e891bb2 --- /dev/null +++ b/tests/components/b_parasite/test.esp32.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + - platform: b_parasite + mac_address: F0:CA:F0:CA:01:01 + humidity: + name: b-parasite Air Humidity + temperature: + name: b-parasite Air Temperature + moisture: + name: b-parasite Soil Moisture + battery_voltage: + name: b-parasite Battery Voltage + illuminance: + name: b-parasite Illuminance diff --git a/tests/components/ballu/test.esp32.yaml b/tests/components/ballu/test.esp32.yaml new file mode 100644 index 0000000000..bb7b9b0435 --- /dev/null +++ b/tests/components/ballu/test.esp32.yaml @@ -0,0 +1,12 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: heatpumpir + protocol: ballu + horizontal_default: middle + vertical_default: middle + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 diff --git a/tests/components/ballu/test.esp8266.yaml b/tests/components/ballu/test.esp8266.yaml new file mode 100644 index 0000000000..05aa446739 --- /dev/null +++ b/tests/components/ballu/test.esp8266.yaml @@ -0,0 +1,12 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: heatpumpir + protocol: ballu + horizontal_default: middle + vertical_default: middle + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 diff --git a/tests/components/bang_bang/test.esp32-c3-idf.yaml b/tests/components/bang_bang/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..5882025191 --- /dev/null +++ b/tests/components/bang_bang/test.esp32-c3-idf.yaml @@ -0,0 +1,35 @@ +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +sensor: + - platform: template + id: template_sensor1 + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 60s + +climate: + - platform: bang_bang + name: Bang Bang Climate + sensor: template_sensor1 + humidity_sensor: template_sensor1 + default_target_temperature_low: 18°C + default_target_temperature_high: 24°C + idle_action: + - switch.turn_on: template_switch1 + cool_action: + - switch.turn_on: template_switch2 + heat_action: + - switch.turn_on: template_switch1 + away_config: + default_target_temperature_low: 16°C + default_target_temperature_high: 20°C diff --git a/tests/components/bang_bang/test.esp32-c3.yaml b/tests/components/bang_bang/test.esp32-c3.yaml new file mode 100644 index 0000000000..5882025191 --- /dev/null +++ b/tests/components/bang_bang/test.esp32-c3.yaml @@ -0,0 +1,35 @@ +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +sensor: + - platform: template + id: template_sensor1 + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 60s + +climate: + - platform: bang_bang + name: Bang Bang Climate + sensor: template_sensor1 + humidity_sensor: template_sensor1 + default_target_temperature_low: 18°C + default_target_temperature_high: 24°C + idle_action: + - switch.turn_on: template_switch1 + cool_action: + - switch.turn_on: template_switch2 + heat_action: + - switch.turn_on: template_switch1 + away_config: + default_target_temperature_low: 16°C + default_target_temperature_high: 20°C diff --git a/tests/components/bang_bang/test.esp32-idf.yaml b/tests/components/bang_bang/test.esp32-idf.yaml new file mode 100644 index 0000000000..5882025191 --- /dev/null +++ b/tests/components/bang_bang/test.esp32-idf.yaml @@ -0,0 +1,35 @@ +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +sensor: + - platform: template + id: template_sensor1 + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 60s + +climate: + - platform: bang_bang + name: Bang Bang Climate + sensor: template_sensor1 + humidity_sensor: template_sensor1 + default_target_temperature_low: 18°C + default_target_temperature_high: 24°C + idle_action: + - switch.turn_on: template_switch1 + cool_action: + - switch.turn_on: template_switch2 + heat_action: + - switch.turn_on: template_switch1 + away_config: + default_target_temperature_low: 16°C + default_target_temperature_high: 20°C diff --git a/tests/components/bang_bang/test.esp32.yaml b/tests/components/bang_bang/test.esp32.yaml new file mode 100644 index 0000000000..5882025191 --- /dev/null +++ b/tests/components/bang_bang/test.esp32.yaml @@ -0,0 +1,35 @@ +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +sensor: + - platform: template + id: template_sensor1 + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 60s + +climate: + - platform: bang_bang + name: Bang Bang Climate + sensor: template_sensor1 + humidity_sensor: template_sensor1 + default_target_temperature_low: 18°C + default_target_temperature_high: 24°C + idle_action: + - switch.turn_on: template_switch1 + cool_action: + - switch.turn_on: template_switch2 + heat_action: + - switch.turn_on: template_switch1 + away_config: + default_target_temperature_low: 16°C + default_target_temperature_high: 20°C diff --git a/tests/components/bang_bang/test.esp8266.yaml b/tests/components/bang_bang/test.esp8266.yaml new file mode 100644 index 0000000000..5882025191 --- /dev/null +++ b/tests/components/bang_bang/test.esp8266.yaml @@ -0,0 +1,35 @@ +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +sensor: + - platform: template + id: template_sensor1 + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 60s + +climate: + - platform: bang_bang + name: Bang Bang Climate + sensor: template_sensor1 + humidity_sensor: template_sensor1 + default_target_temperature_low: 18°C + default_target_temperature_high: 24°C + idle_action: + - switch.turn_on: template_switch1 + cool_action: + - switch.turn_on: template_switch2 + heat_action: + - switch.turn_on: template_switch1 + away_config: + default_target_temperature_low: 16°C + default_target_temperature_high: 20°C diff --git a/tests/components/bang_bang/test.rp2040.yaml b/tests/components/bang_bang/test.rp2040.yaml new file mode 100644 index 0000000000..5882025191 --- /dev/null +++ b/tests/components/bang_bang/test.rp2040.yaml @@ -0,0 +1,35 @@ +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +sensor: + - platform: template + id: template_sensor1 + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 60s + +climate: + - platform: bang_bang + name: Bang Bang Climate + sensor: template_sensor1 + humidity_sensor: template_sensor1 + default_target_temperature_low: 18°C + default_target_temperature_high: 24°C + idle_action: + - switch.turn_on: template_switch1 + cool_action: + - switch.turn_on: template_switch2 + heat_action: + - switch.turn_on: template_switch1 + away_config: + default_target_temperature_low: 16°C + default_target_temperature_high: 20°C diff --git a/tests/components/bedjet/test.esp32-c3-idf.yaml b/tests/components/bedjet/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..c2be04a49a --- /dev/null +++ b/tests/components/bedjet/test.esp32-c3-idf.yaml @@ -0,0 +1,33 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + id: sntp_time + servers: + - 0.pool.ntp.org + - 1.pool.ntp.org + - 192.168.178.1 + +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: bedjet_blec + +bedjet: + - id: bedjet_hub + ble_client_id: bedjet_blec + time_id: sntp_time + +climate: + - platform: bedjet + name: My Bedjet + bedjet_id: bedjet_hub + heat_mode: extended + +fan: + - platform: bedjet + name: My Bedjet fan + bedjet_id: bedjet_hub diff --git a/tests/components/bedjet/test.esp32-c3.yaml b/tests/components/bedjet/test.esp32-c3.yaml new file mode 100644 index 0000000000..c2be04a49a --- /dev/null +++ b/tests/components/bedjet/test.esp32-c3.yaml @@ -0,0 +1,33 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + id: sntp_time + servers: + - 0.pool.ntp.org + - 1.pool.ntp.org + - 192.168.178.1 + +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: bedjet_blec + +bedjet: + - id: bedjet_hub + ble_client_id: bedjet_blec + time_id: sntp_time + +climate: + - platform: bedjet + name: My Bedjet + bedjet_id: bedjet_hub + heat_mode: extended + +fan: + - platform: bedjet + name: My Bedjet fan + bedjet_id: bedjet_hub diff --git a/tests/components/bedjet/test.esp32-idf.yaml b/tests/components/bedjet/test.esp32-idf.yaml new file mode 100644 index 0000000000..c2be04a49a --- /dev/null +++ b/tests/components/bedjet/test.esp32-idf.yaml @@ -0,0 +1,33 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + id: sntp_time + servers: + - 0.pool.ntp.org + - 1.pool.ntp.org + - 192.168.178.1 + +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: bedjet_blec + +bedjet: + - id: bedjet_hub + ble_client_id: bedjet_blec + time_id: sntp_time + +climate: + - platform: bedjet + name: My Bedjet + bedjet_id: bedjet_hub + heat_mode: extended + +fan: + - platform: bedjet + name: My Bedjet fan + bedjet_id: bedjet_hub diff --git a/tests/components/bedjet/test.esp32.yaml b/tests/components/bedjet/test.esp32.yaml new file mode 100644 index 0000000000..c2be04a49a --- /dev/null +++ b/tests/components/bedjet/test.esp32.yaml @@ -0,0 +1,33 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + id: sntp_time + servers: + - 0.pool.ntp.org + - 1.pool.ntp.org + - 192.168.178.1 + +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: bedjet_blec + +bedjet: + - id: bedjet_hub + ble_client_id: bedjet_blec + time_id: sntp_time + +climate: + - platform: bedjet + name: My Bedjet + bedjet_id: bedjet_hub + heat_mode: extended + +fan: + - platform: bedjet + name: My Bedjet fan + bedjet_id: bedjet_hub diff --git a/tests/components/bh1750/test.esp32-c3-idf.yaml b/tests/components/bh1750/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..e367de3845 --- /dev/null +++ b/tests/components/bh1750/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_bh1750 + scl: 5 + sda: 4 + +sensor: + - platform: bh1750 + name: Living Room Brightness + address: 0x23 + update_interval: 30s diff --git a/tests/components/bh1750/test.esp32-c3.yaml b/tests/components/bh1750/test.esp32-c3.yaml new file mode 100644 index 0000000000..e367de3845 --- /dev/null +++ b/tests/components/bh1750/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_bh1750 + scl: 5 + sda: 4 + +sensor: + - platform: bh1750 + name: Living Room Brightness + address: 0x23 + update_interval: 30s diff --git a/tests/components/bh1750/test.esp32-idf.yaml b/tests/components/bh1750/test.esp32-idf.yaml new file mode 100644 index 0000000000..b10ec231ae --- /dev/null +++ b/tests/components/bh1750/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_bh1750 + scl: 16 + sda: 17 + +sensor: + - platform: bh1750 + name: Living Room Brightness + address: 0x23 + update_interval: 30s diff --git a/tests/components/bh1750/test.esp32.yaml b/tests/components/bh1750/test.esp32.yaml new file mode 100644 index 0000000000..b10ec231ae --- /dev/null +++ b/tests/components/bh1750/test.esp32.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_bh1750 + scl: 16 + sda: 17 + +sensor: + - platform: bh1750 + name: Living Room Brightness + address: 0x23 + update_interval: 30s diff --git a/tests/components/bh1750/test.esp8266.yaml b/tests/components/bh1750/test.esp8266.yaml new file mode 100644 index 0000000000..e367de3845 --- /dev/null +++ b/tests/components/bh1750/test.esp8266.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_bh1750 + scl: 5 + sda: 4 + +sensor: + - platform: bh1750 + name: Living Room Brightness + address: 0x23 + update_interval: 30s diff --git a/tests/components/bh1750/test.rp2040.yaml b/tests/components/bh1750/test.rp2040.yaml new file mode 100644 index 0000000000..e367de3845 --- /dev/null +++ b/tests/components/bh1750/test.rp2040.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_bh1750 + scl: 5 + sda: 4 + +sensor: + - platform: bh1750 + name: Living Room Brightness + address: 0x23 + update_interval: 30s diff --git a/tests/components/binary_sensor_map/test.esp32-c3-idf.yaml b/tests/components/binary_sensor_map/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..8ffdd1f379 --- /dev/null +++ b/tests/components/binary_sensor_map/test.esp32-c3-idf.yaml @@ -0,0 +1,61 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + - platform: template + id: bin2 + lambda: |- + if (millis() > 20000) { + return true; + } else { + return false; + } + - platform: template + id: bin3 + lambda: |- + if (millis() > 30000) { + return true; + } else { + return false; + } + +sensor: + - platform: binary_sensor_map + name: Binary Sensor Map + type: group + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: sum + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: bayesian + prior: 0.4 + observations: + - binary_sensor: bin1 + prob_given_true: 0.9 + prob_given_false: 0.4 + - binary_sensor: bin2 + prob_given_true: 0.7 + prob_given_false: 0.05 + - binary_sensor: bin3 + prob_given_true: 0.8 + prob_given_false: 0.2 diff --git a/tests/components/binary_sensor_map/test.esp32-c3.yaml b/tests/components/binary_sensor_map/test.esp32-c3.yaml new file mode 100644 index 0000000000..8ffdd1f379 --- /dev/null +++ b/tests/components/binary_sensor_map/test.esp32-c3.yaml @@ -0,0 +1,61 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + - platform: template + id: bin2 + lambda: |- + if (millis() > 20000) { + return true; + } else { + return false; + } + - platform: template + id: bin3 + lambda: |- + if (millis() > 30000) { + return true; + } else { + return false; + } + +sensor: + - platform: binary_sensor_map + name: Binary Sensor Map + type: group + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: sum + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: bayesian + prior: 0.4 + observations: + - binary_sensor: bin1 + prob_given_true: 0.9 + prob_given_false: 0.4 + - binary_sensor: bin2 + prob_given_true: 0.7 + prob_given_false: 0.05 + - binary_sensor: bin3 + prob_given_true: 0.8 + prob_given_false: 0.2 diff --git a/tests/components/binary_sensor_map/test.esp32-idf.yaml b/tests/components/binary_sensor_map/test.esp32-idf.yaml new file mode 100644 index 0000000000..8ffdd1f379 --- /dev/null +++ b/tests/components/binary_sensor_map/test.esp32-idf.yaml @@ -0,0 +1,61 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + - platform: template + id: bin2 + lambda: |- + if (millis() > 20000) { + return true; + } else { + return false; + } + - platform: template + id: bin3 + lambda: |- + if (millis() > 30000) { + return true; + } else { + return false; + } + +sensor: + - platform: binary_sensor_map + name: Binary Sensor Map + type: group + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: sum + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: bayesian + prior: 0.4 + observations: + - binary_sensor: bin1 + prob_given_true: 0.9 + prob_given_false: 0.4 + - binary_sensor: bin2 + prob_given_true: 0.7 + prob_given_false: 0.05 + - binary_sensor: bin3 + prob_given_true: 0.8 + prob_given_false: 0.2 diff --git a/tests/components/binary_sensor_map/test.esp32.yaml b/tests/components/binary_sensor_map/test.esp32.yaml new file mode 100644 index 0000000000..8ffdd1f379 --- /dev/null +++ b/tests/components/binary_sensor_map/test.esp32.yaml @@ -0,0 +1,61 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + - platform: template + id: bin2 + lambda: |- + if (millis() > 20000) { + return true; + } else { + return false; + } + - platform: template + id: bin3 + lambda: |- + if (millis() > 30000) { + return true; + } else { + return false; + } + +sensor: + - platform: binary_sensor_map + name: Binary Sensor Map + type: group + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: sum + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: bayesian + prior: 0.4 + observations: + - binary_sensor: bin1 + prob_given_true: 0.9 + prob_given_false: 0.4 + - binary_sensor: bin2 + prob_given_true: 0.7 + prob_given_false: 0.05 + - binary_sensor: bin3 + prob_given_true: 0.8 + prob_given_false: 0.2 diff --git a/tests/components/binary_sensor_map/test.esp8266.yaml b/tests/components/binary_sensor_map/test.esp8266.yaml new file mode 100644 index 0000000000..8ffdd1f379 --- /dev/null +++ b/tests/components/binary_sensor_map/test.esp8266.yaml @@ -0,0 +1,61 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + - platform: template + id: bin2 + lambda: |- + if (millis() > 20000) { + return true; + } else { + return false; + } + - platform: template + id: bin3 + lambda: |- + if (millis() > 30000) { + return true; + } else { + return false; + } + +sensor: + - platform: binary_sensor_map + name: Binary Sensor Map + type: group + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: sum + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: bayesian + prior: 0.4 + observations: + - binary_sensor: bin1 + prob_given_true: 0.9 + prob_given_false: 0.4 + - binary_sensor: bin2 + prob_given_true: 0.7 + prob_given_false: 0.05 + - binary_sensor: bin3 + prob_given_true: 0.8 + prob_given_false: 0.2 diff --git a/tests/components/binary_sensor_map/test.rp2040.yaml b/tests/components/binary_sensor_map/test.rp2040.yaml new file mode 100644 index 0000000000..8ffdd1f379 --- /dev/null +++ b/tests/components/binary_sensor_map/test.rp2040.yaml @@ -0,0 +1,61 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + - platform: template + id: bin2 + lambda: |- + if (millis() > 20000) { + return true; + } else { + return false; + } + - platform: template + id: bin3 + lambda: |- + if (millis() > 30000) { + return true; + } else { + return false; + } + +sensor: + - platform: binary_sensor_map + name: Binary Sensor Map + type: group + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: sum + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: bayesian + prior: 0.4 + observations: + - binary_sensor: bin1 + prob_given_true: 0.9 + prob_given_false: 0.4 + - binary_sensor: bin2 + prob_given_true: 0.7 + prob_given_false: 0.05 + - binary_sensor: bin3 + prob_given_true: 0.8 + prob_given_false: 0.2 diff --git a/tests/components/bl0939/test.esp32-c3-idf.yaml b/tests/components/bl0939/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4c92ccb7dd --- /dev/null +++ b/tests/components/bl0939/test.esp32-c3-idf.yaml @@ -0,0 +1,26 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0939 + voltage: + name: BL0939 Voltage + current_1: + name: BL0939 Current 1 + current_2: + name: BL0939 Current 2 + active_power_1: + name: BL0939 Active Power 1 + active_power_2: + name: BL0939 Active Power 2 + energy_1: + name: BL0939 Energy 1 + energy_2: + name: BL0939 Energy 2 + energy_total: + name: BL0939 Total energy diff --git a/tests/components/bl0939/test.esp32-c3.yaml b/tests/components/bl0939/test.esp32-c3.yaml new file mode 100644 index 0000000000..4c92ccb7dd --- /dev/null +++ b/tests/components/bl0939/test.esp32-c3.yaml @@ -0,0 +1,26 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0939 + voltage: + name: BL0939 Voltage + current_1: + name: BL0939 Current 1 + current_2: + name: BL0939 Current 2 + active_power_1: + name: BL0939 Active Power 1 + active_power_2: + name: BL0939 Active Power 2 + energy_1: + name: BL0939 Energy 1 + energy_2: + name: BL0939 Energy 2 + energy_total: + name: BL0939 Total energy diff --git a/tests/components/bl0939/test.esp32-idf.yaml b/tests/components/bl0939/test.esp32-idf.yaml new file mode 100644 index 0000000000..df0e683b2f --- /dev/null +++ b/tests/components/bl0939/test.esp32-idf.yaml @@ -0,0 +1,26 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: bl0939 + voltage: + name: BL0939 Voltage + current_1: + name: BL0939 Current 1 + current_2: + name: BL0939 Current 2 + active_power_1: + name: BL0939 Active Power 1 + active_power_2: + name: BL0939 Active Power 2 + energy_1: + name: BL0939 Energy 1 + energy_2: + name: BL0939 Energy 2 + energy_total: + name: BL0939 Total energy diff --git a/tests/components/bl0939/test.esp32.yaml b/tests/components/bl0939/test.esp32.yaml new file mode 100644 index 0000000000..df0e683b2f --- /dev/null +++ b/tests/components/bl0939/test.esp32.yaml @@ -0,0 +1,26 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: bl0939 + voltage: + name: BL0939 Voltage + current_1: + name: BL0939 Current 1 + current_2: + name: BL0939 Current 2 + active_power_1: + name: BL0939 Active Power 1 + active_power_2: + name: BL0939 Active Power 2 + energy_1: + name: BL0939 Energy 1 + energy_2: + name: BL0939 Energy 2 + energy_total: + name: BL0939 Total energy diff --git a/tests/components/bl0939/test.esp8266.yaml b/tests/components/bl0939/test.esp8266.yaml new file mode 100644 index 0000000000..4c92ccb7dd --- /dev/null +++ b/tests/components/bl0939/test.esp8266.yaml @@ -0,0 +1,26 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0939 + voltage: + name: BL0939 Voltage + current_1: + name: BL0939 Current 1 + current_2: + name: BL0939 Current 2 + active_power_1: + name: BL0939 Active Power 1 + active_power_2: + name: BL0939 Active Power 2 + energy_1: + name: BL0939 Energy 1 + energy_2: + name: BL0939 Energy 2 + energy_total: + name: BL0939 Total energy diff --git a/tests/components/bl0939/test.rp2040.yaml b/tests/components/bl0939/test.rp2040.yaml new file mode 100644 index 0000000000..4c92ccb7dd --- /dev/null +++ b/tests/components/bl0939/test.rp2040.yaml @@ -0,0 +1,26 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0939 + voltage: + name: BL0939 Voltage + current_1: + name: BL0939 Current 1 + current_2: + name: BL0939 Current 2 + active_power_1: + name: BL0939 Active Power 1 + active_power_2: + name: BL0939 Active Power 2 + energy_1: + name: BL0939 Energy 1 + energy_2: + name: BL0939 Energy 2 + energy_total: + name: BL0939 Total energy diff --git a/tests/components/bl0940/test.esp32-c3-idf.yaml b/tests/components/bl0940/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..a20f785b02 --- /dev/null +++ b/tests/components/bl0940/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0940 + voltage: + name: BL0940 Voltage + current: + name: BL0940 Current + power: + name: BL0940 Power + energy: + name: BL0940 Energy + internal_temperature: + name: BL0940 Internal temperature + external_temperature: + name: BL0940 External temperature diff --git a/tests/components/bl0940/test.esp32-c3.yaml b/tests/components/bl0940/test.esp32-c3.yaml new file mode 100644 index 0000000000..a20f785b02 --- /dev/null +++ b/tests/components/bl0940/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0940 + voltage: + name: BL0940 Voltage + current: + name: BL0940 Current + power: + name: BL0940 Power + energy: + name: BL0940 Energy + internal_temperature: + name: BL0940 Internal temperature + external_temperature: + name: BL0940 External temperature diff --git a/tests/components/bl0940/test.esp32-idf.yaml b/tests/components/bl0940/test.esp32-idf.yaml new file mode 100644 index 0000000000..c7d97ca3b9 --- /dev/null +++ b/tests/components/bl0940/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: bl0940 + voltage: + name: BL0940 Voltage + current: + name: BL0940 Current + power: + name: BL0940 Power + energy: + name: BL0940 Energy + internal_temperature: + name: BL0940 Internal temperature + external_temperature: + name: BL0940 External temperature diff --git a/tests/components/bl0940/test.esp32.yaml b/tests/components/bl0940/test.esp32.yaml new file mode 100644 index 0000000000..c7d97ca3b9 --- /dev/null +++ b/tests/components/bl0940/test.esp32.yaml @@ -0,0 +1,22 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: bl0940 + voltage: + name: BL0940 Voltage + current: + name: BL0940 Current + power: + name: BL0940 Power + energy: + name: BL0940 Energy + internal_temperature: + name: BL0940 Internal temperature + external_temperature: + name: BL0940 External temperature diff --git a/tests/components/bl0940/test.esp8266.yaml b/tests/components/bl0940/test.esp8266.yaml new file mode 100644 index 0000000000..a20f785b02 --- /dev/null +++ b/tests/components/bl0940/test.esp8266.yaml @@ -0,0 +1,22 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0940 + voltage: + name: BL0940 Voltage + current: + name: BL0940 Current + power: + name: BL0940 Power + energy: + name: BL0940 Energy + internal_temperature: + name: BL0940 Internal temperature + external_temperature: + name: BL0940 External temperature diff --git a/tests/components/bl0940/test.rp2040.yaml b/tests/components/bl0940/test.rp2040.yaml new file mode 100644 index 0000000000..a20f785b02 --- /dev/null +++ b/tests/components/bl0940/test.rp2040.yaml @@ -0,0 +1,22 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0940 + voltage: + name: BL0940 Voltage + current: + name: BL0940 Current + power: + name: BL0940 Power + energy: + name: BL0940 Energy + internal_temperature: + name: BL0940 Internal temperature + external_temperature: + name: BL0940 External temperature diff --git a/tests/components/bl0942/test.esp32-c3-idf.yaml b/tests/components/bl0942/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..8d16efed4f --- /dev/null +++ b/tests/components/bl0942/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0942 + voltage: + name: BL0942 Voltage + current: + name: BL0942 Current + power: + name: BL0942 Power + energy: + name: BL0942 Energy + frequency: + name: BL0942 Frequency diff --git a/tests/components/bl0942/test.esp32-c3.yaml b/tests/components/bl0942/test.esp32-c3.yaml new file mode 100644 index 0000000000..8d16efed4f --- /dev/null +++ b/tests/components/bl0942/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0942 + voltage: + name: BL0942 Voltage + current: + name: BL0942 Current + power: + name: BL0942 Power + energy: + name: BL0942 Energy + frequency: + name: BL0942 Frequency diff --git a/tests/components/bl0942/test.esp32-idf.yaml b/tests/components/bl0942/test.esp32-idf.yaml new file mode 100644 index 0000000000..45ac85aa2a --- /dev/null +++ b/tests/components/bl0942/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: bl0942 + voltage: + name: BL0942 Voltage + current: + name: BL0942 Current + power: + name: BL0942 Power + energy: + name: BL0942 Energy + frequency: + name: BL0942 Frequency diff --git a/tests/components/bl0942/test.esp32.yaml b/tests/components/bl0942/test.esp32.yaml new file mode 100644 index 0000000000..45ac85aa2a --- /dev/null +++ b/tests/components/bl0942/test.esp32.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: bl0942 + voltage: + name: BL0942 Voltage + current: + name: BL0942 Current + power: + name: BL0942 Power + energy: + name: BL0942 Energy + frequency: + name: BL0942 Frequency diff --git a/tests/components/bl0942/test.esp8266.yaml b/tests/components/bl0942/test.esp8266.yaml new file mode 100644 index 0000000000..8d16efed4f --- /dev/null +++ b/tests/components/bl0942/test.esp8266.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0942 + voltage: + name: BL0942 Voltage + current: + name: BL0942 Current + power: + name: BL0942 Power + energy: + name: BL0942 Energy + frequency: + name: BL0942 Frequency diff --git a/tests/components/bl0942/test.rp2040.yaml b/tests/components/bl0942/test.rp2040.yaml new file mode 100644 index 0000000000..8d16efed4f --- /dev/null +++ b/tests/components/bl0942/test.rp2040.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0942 + voltage: + name: BL0942 Voltage + current: + name: BL0942 Current + power: + name: BL0942 Power + energy: + name: BL0942 Energy + frequency: + name: BL0942 Frequency diff --git a/tests/components/ble_client/test.esp32-c3-idf.yaml b/tests/components/ble_client/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..b5272d01f0 --- /dev/null +++ b/tests/components/ble_client/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: test_blec diff --git a/tests/components/ble_client/test.esp32-c3.yaml b/tests/components/ble_client/test.esp32-c3.yaml new file mode 100644 index 0000000000..b5272d01f0 --- /dev/null +++ b/tests/components/ble_client/test.esp32-c3.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: test_blec diff --git a/tests/components/ble_client/test.esp32-idf.yaml b/tests/components/ble_client/test.esp32-idf.yaml new file mode 100644 index 0000000000..b5272d01f0 --- /dev/null +++ b/tests/components/ble_client/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: test_blec diff --git a/tests/components/ble_client/test.esp32.yaml b/tests/components/ble_client/test.esp32.yaml new file mode 100644 index 0000000000..b5272d01f0 --- /dev/null +++ b/tests/components/ble_client/test.esp32.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: test_blec diff --git a/tests/components/ble_presence/test.esp32-c3-idf.yaml b/tests/components/ble_presence/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..6e5173eed8 --- /dev/null +++ b/tests/components/ble_presence/test.esp32-c3-idf.yaml @@ -0,0 +1,24 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: ble_presence + mac_address: AC:37:43:77:5F:4C + name: ESP32 BLE Tracker Google Home Mini + - platform: ble_presence + service_uuid: 11aa + name: BLE Test Service 16 Presence + - platform: ble_presence + service_uuid: "11223344" + name: BLE Test Service 32 Presence + - platform: ble_presence + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test Service 128 Presence + - platform: ble_presence + ibeacon_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + ibeacon_major: 100 + ibeacon_minor: 1 + name: BLE Test iBeacon Presence + - platform: ble_presence + irk: 1234567890abcdef1234567890abcdef + name: "ESP32 BLE Tracker with Identity Resolving Key" + diff --git a/tests/components/ble_presence/test.esp32-c3.yaml b/tests/components/ble_presence/test.esp32-c3.yaml new file mode 100644 index 0000000000..6e5173eed8 --- /dev/null +++ b/tests/components/ble_presence/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: ble_presence + mac_address: AC:37:43:77:5F:4C + name: ESP32 BLE Tracker Google Home Mini + - platform: ble_presence + service_uuid: 11aa + name: BLE Test Service 16 Presence + - platform: ble_presence + service_uuid: "11223344" + name: BLE Test Service 32 Presence + - platform: ble_presence + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test Service 128 Presence + - platform: ble_presence + ibeacon_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + ibeacon_major: 100 + ibeacon_minor: 1 + name: BLE Test iBeacon Presence + - platform: ble_presence + irk: 1234567890abcdef1234567890abcdef + name: "ESP32 BLE Tracker with Identity Resolving Key" + diff --git a/tests/components/ble_presence/test.esp32-idf.yaml b/tests/components/ble_presence/test.esp32-idf.yaml new file mode 100644 index 0000000000..6e5173eed8 --- /dev/null +++ b/tests/components/ble_presence/test.esp32-idf.yaml @@ -0,0 +1,24 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: ble_presence + mac_address: AC:37:43:77:5F:4C + name: ESP32 BLE Tracker Google Home Mini + - platform: ble_presence + service_uuid: 11aa + name: BLE Test Service 16 Presence + - platform: ble_presence + service_uuid: "11223344" + name: BLE Test Service 32 Presence + - platform: ble_presence + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test Service 128 Presence + - platform: ble_presence + ibeacon_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + ibeacon_major: 100 + ibeacon_minor: 1 + name: BLE Test iBeacon Presence + - platform: ble_presence + irk: 1234567890abcdef1234567890abcdef + name: "ESP32 BLE Tracker with Identity Resolving Key" + diff --git a/tests/components/ble_presence/test.esp32.yaml b/tests/components/ble_presence/test.esp32.yaml new file mode 100644 index 0000000000..6e5173eed8 --- /dev/null +++ b/tests/components/ble_presence/test.esp32.yaml @@ -0,0 +1,24 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: ble_presence + mac_address: AC:37:43:77:5F:4C + name: ESP32 BLE Tracker Google Home Mini + - platform: ble_presence + service_uuid: 11aa + name: BLE Test Service 16 Presence + - platform: ble_presence + service_uuid: "11223344" + name: BLE Test Service 32 Presence + - platform: ble_presence + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test Service 128 Presence + - platform: ble_presence + ibeacon_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + ibeacon_major: 100 + ibeacon_minor: 1 + name: BLE Test iBeacon Presence + - platform: ble_presence + irk: 1234567890abcdef1234567890abcdef + name: "ESP32 BLE Tracker with Identity Resolving Key" + diff --git a/tests/components/ble_rssi/test.esp32-c3-idf.yaml b/tests/components/ble_rssi/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..52e5b865c6 --- /dev/null +++ b/tests/components/ble_rssi/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +esp32_ble_tracker: + +sensor: + - platform: ble_rssi + mac_address: AC:37:43:77:5F:4C + name: BLE Google Home Mini RSSI value + - platform: ble_rssi + service_uuid: 11aa + name: BLE Test Service 16 + - platform: ble_rssi + service_uuid: "11223344" + name: BLE Test Service 32 + - platform: ble_rssi + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test Service 128 + - platform: ble_rssi + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test iBeacon UUID diff --git a/tests/components/ble_rssi/test.esp32-c3.yaml b/tests/components/ble_rssi/test.esp32-c3.yaml new file mode 100644 index 0000000000..52e5b865c6 --- /dev/null +++ b/tests/components/ble_rssi/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +esp32_ble_tracker: + +sensor: + - platform: ble_rssi + mac_address: AC:37:43:77:5F:4C + name: BLE Google Home Mini RSSI value + - platform: ble_rssi + service_uuid: 11aa + name: BLE Test Service 16 + - platform: ble_rssi + service_uuid: "11223344" + name: BLE Test Service 32 + - platform: ble_rssi + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test Service 128 + - platform: ble_rssi + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test iBeacon UUID diff --git a/tests/components/ble_rssi/test.esp32-idf.yaml b/tests/components/ble_rssi/test.esp32-idf.yaml new file mode 100644 index 0000000000..52e5b865c6 --- /dev/null +++ b/tests/components/ble_rssi/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +esp32_ble_tracker: + +sensor: + - platform: ble_rssi + mac_address: AC:37:43:77:5F:4C + name: BLE Google Home Mini RSSI value + - platform: ble_rssi + service_uuid: 11aa + name: BLE Test Service 16 + - platform: ble_rssi + service_uuid: "11223344" + name: BLE Test Service 32 + - platform: ble_rssi + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test Service 128 + - platform: ble_rssi + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test iBeacon UUID diff --git a/tests/components/ble_rssi/test.esp32.yaml b/tests/components/ble_rssi/test.esp32.yaml new file mode 100644 index 0000000000..52e5b865c6 --- /dev/null +++ b/tests/components/ble_rssi/test.esp32.yaml @@ -0,0 +1,18 @@ +esp32_ble_tracker: + +sensor: + - platform: ble_rssi + mac_address: AC:37:43:77:5F:4C + name: BLE Google Home Mini RSSI value + - platform: ble_rssi + service_uuid: 11aa + name: BLE Test Service 16 + - platform: ble_rssi + service_uuid: "11223344" + name: BLE Test Service 32 + - platform: ble_rssi + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test Service 128 + - platform: ble_rssi + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test iBeacon UUID diff --git a/tests/components/ble_scanner/test.esp32-c3-idf.yaml b/tests/components/ble_scanner/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..935a5a5a19 --- /dev/null +++ b/tests/components/ble_scanner/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +text_sensor: + - platform: ble_scanner + name: Scanner diff --git a/tests/components/ble_scanner/test.esp32-c3.yaml b/tests/components/ble_scanner/test.esp32-c3.yaml new file mode 100644 index 0000000000..935a5a5a19 --- /dev/null +++ b/tests/components/ble_scanner/test.esp32-c3.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +text_sensor: + - platform: ble_scanner + name: Scanner diff --git a/tests/components/ble_scanner/test.esp32-idf.yaml b/tests/components/ble_scanner/test.esp32-idf.yaml new file mode 100644 index 0000000000..935a5a5a19 --- /dev/null +++ b/tests/components/ble_scanner/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +text_sensor: + - platform: ble_scanner + name: Scanner diff --git a/tests/components/ble_scanner/test.esp32.yaml b/tests/components/ble_scanner/test.esp32.yaml new file mode 100644 index 0000000000..935a5a5a19 --- /dev/null +++ b/tests/components/ble_scanner/test.esp32.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +text_sensor: + - platform: ble_scanner + name: Scanner diff --git a/tests/components/bme280_i2c/test.esp32-c3-idf.yaml b/tests/components/bme280_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..070c2845d9 --- /dev/null +++ b/tests/components/bme280_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +sensor: + - platform: bme280_i2c + address: 0x76 + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme280_i2c/test.esp32-c3.yaml b/tests/components/bme280_i2c/test.esp32-c3.yaml new file mode 100644 index 0000000000..070c2845d9 --- /dev/null +++ b/tests/components/bme280_i2c/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +sensor: + - platform: bme280_i2c + address: 0x76 + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme280_i2c/test.esp32-idf.yaml b/tests/components/bme280_i2c/test.esp32-idf.yaml new file mode 100644 index 0000000000..e379b98874 --- /dev/null +++ b/tests/components/bme280_i2c/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_bme280 + scl: 16 + sda: 17 + +sensor: + - platform: bme280_i2c + address: 0x76 + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme280_i2c/test.esp32.yaml b/tests/components/bme280_i2c/test.esp32.yaml new file mode 100644 index 0000000000..e379b98874 --- /dev/null +++ b/tests/components/bme280_i2c/test.esp32.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_bme280 + scl: 16 + sda: 17 + +sensor: + - platform: bme280_i2c + address: 0x76 + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme280_i2c/test.esp8266.yaml b/tests/components/bme280_i2c/test.esp8266.yaml new file mode 100644 index 0000000000..070c2845d9 --- /dev/null +++ b/tests/components/bme280_i2c/test.esp8266.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +sensor: + - platform: bme280_i2c + address: 0x76 + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme280_i2c/test.rp2040.yaml b/tests/components/bme280_i2c/test.rp2040.yaml new file mode 100644 index 0000000000..070c2845d9 --- /dev/null +++ b/tests/components/bme280_i2c/test.rp2040.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +sensor: + - platform: bme280_i2c + address: 0x76 + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme280_spi/test.esp32-c3-idf.yaml b/tests/components/bme280_spi/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4bc7c14e6c --- /dev/null +++ b/tests/components/bme280_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,19 @@ +spi: + - id: spi_bme280 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: bme280_spi + cs_pin: 8 + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme280_spi/test.esp32-c3.yaml b/tests/components/bme280_spi/test.esp32-c3.yaml new file mode 100644 index 0000000000..4bc7c14e6c --- /dev/null +++ b/tests/components/bme280_spi/test.esp32-c3.yaml @@ -0,0 +1,19 @@ +spi: + - id: spi_bme280 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: bme280_spi + cs_pin: 8 + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme280_spi/test.esp32-idf.yaml b/tests/components/bme280_spi/test.esp32-idf.yaml new file mode 100644 index 0000000000..ebb3d98213 --- /dev/null +++ b/tests/components/bme280_spi/test.esp32-idf.yaml @@ -0,0 +1,19 @@ +spi: + - id: spi_bme280 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: bme280_spi + cs_pin: 12 + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme280_spi/test.esp32.yaml b/tests/components/bme280_spi/test.esp32.yaml new file mode 100644 index 0000000000..ebb3d98213 --- /dev/null +++ b/tests/components/bme280_spi/test.esp32.yaml @@ -0,0 +1,19 @@ +spi: + - id: spi_bme280 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: bme280_spi + cs_pin: 12 + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme280_spi/test.esp8266.yaml b/tests/components/bme280_spi/test.esp8266.yaml new file mode 100644 index 0000000000..63013abb87 --- /dev/null +++ b/tests/components/bme280_spi/test.esp8266.yaml @@ -0,0 +1,19 @@ +spi: + - id: spi_bme280 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: bme280_spi + cs_pin: 15 + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme280_spi/test.rp2040.yaml b/tests/components/bme280_spi/test.rp2040.yaml new file mode 100644 index 0000000000..ba5cb483c6 --- /dev/null +++ b/tests/components/bme280_spi/test.rp2040.yaml @@ -0,0 +1,19 @@ +spi: + - id: spi_bme280 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: bme280_spi + cs_pin: 6 + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme680/test.esp32-c3-idf.yaml b/tests/components/bme680/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..f12be09d20 --- /dev/null +++ b/tests/components/bme680/test.esp32-c3-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_bme680 + scl: 5 + sda: 4 + +sensor: + - platform: bme680 + temperature: + name: BME680 Temperature + oversampling: 16x + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + address: 0x77 + heater: + temperature: 320 + duration: 150ms + update_interval: 15s diff --git a/tests/components/bme680/test.esp32-c3.yaml b/tests/components/bme680/test.esp32-c3.yaml new file mode 100644 index 0000000000..f12be09d20 --- /dev/null +++ b/tests/components/bme680/test.esp32-c3.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_bme680 + scl: 5 + sda: 4 + +sensor: + - platform: bme680 + temperature: + name: BME680 Temperature + oversampling: 16x + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + address: 0x77 + heater: + temperature: 320 + duration: 150ms + update_interval: 15s diff --git a/tests/components/bme680/test.esp32-idf.yaml b/tests/components/bme680/test.esp32-idf.yaml new file mode 100644 index 0000000000..04d0ed8fe4 --- /dev/null +++ b/tests/components/bme680/test.esp32-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_bme680 + scl: 16 + sda: 17 + +sensor: + - platform: bme680 + temperature: + name: BME680 Temperature + oversampling: 16x + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + address: 0x77 + heater: + temperature: 320 + duration: 150ms + update_interval: 15s diff --git a/tests/components/bme680/test.esp32.yaml b/tests/components/bme680/test.esp32.yaml new file mode 100644 index 0000000000..04d0ed8fe4 --- /dev/null +++ b/tests/components/bme680/test.esp32.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_bme680 + scl: 16 + sda: 17 + +sensor: + - platform: bme680 + temperature: + name: BME680 Temperature + oversampling: 16x + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + address: 0x77 + heater: + temperature: 320 + duration: 150ms + update_interval: 15s diff --git a/tests/components/bme680/test.esp8266.yaml b/tests/components/bme680/test.esp8266.yaml new file mode 100644 index 0000000000..f12be09d20 --- /dev/null +++ b/tests/components/bme680/test.esp8266.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_bme680 + scl: 5 + sda: 4 + +sensor: + - platform: bme680 + temperature: + name: BME680 Temperature + oversampling: 16x + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + address: 0x77 + heater: + temperature: 320 + duration: 150ms + update_interval: 15s diff --git a/tests/components/bme680/test.rp2040.yaml b/tests/components/bme680/test.rp2040.yaml new file mode 100644 index 0000000000..f12be09d20 --- /dev/null +++ b/tests/components/bme680/test.rp2040.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_bme680 + scl: 5 + sda: 4 + +sensor: + - platform: bme680 + temperature: + name: BME680 Temperature + oversampling: 16x + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + address: 0x77 + heater: + temperature: 320 + duration: 150ms + update_interval: 15s diff --git a/tests/components/bme680_bsec/test.esp32.yaml b/tests/components/bme680_bsec/test.esp32.yaml new file mode 100644 index 0000000000..4f62f13abb --- /dev/null +++ b/tests/components/bme680_bsec/test.esp32.yaml @@ -0,0 +1,29 @@ +i2c: + - id: i2c_bme680 + scl: 16 + sda: 17 + +bme680_bsec: + address: 0x77 + +sensor: + - platform: bme680_bsec + temperature: + name: BME680 Temperature + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + iaq: + name: BME680 IAQ + co2_equivalent: + name: BME680 eCO2 + breath_voc_equivalent: + name: BME680 Breath eVOC + +text_sensor: + - platform: bme680_bsec + iaq_accuracy: + name: BME680 Accuracy diff --git a/tests/components/bme680_bsec/test.esp8266.yaml b/tests/components/bme680_bsec/test.esp8266.yaml new file mode 100644 index 0000000000..84b32d3635 --- /dev/null +++ b/tests/components/bme680_bsec/test.esp8266.yaml @@ -0,0 +1,29 @@ +i2c: + - id: i2c_bme680 + scl: 5 + sda: 4 + +bme680_bsec: + address: 0x77 + +sensor: + - platform: bme680_bsec + temperature: + name: BME680 Temperature + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + iaq: + name: BME680 IAQ + co2_equivalent: + name: BME680 eCO2 + breath_voc_equivalent: + name: BME680 Breath eVOC + +text_sensor: + - platform: bme680_bsec + iaq_accuracy: + name: BME680 Accuracy diff --git a/tests/components/bmi160/test.esp32-c3-idf.yaml b/tests/components/bmi160/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..3fd6441980 --- /dev/null +++ b/tests/components/bmi160/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_bmi160 + scl: 5 + sda: 4 + +sensor: + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature diff --git a/tests/components/bmi160/test.esp32-c3.yaml b/tests/components/bmi160/test.esp32-c3.yaml new file mode 100644 index 0000000000..3fd6441980 --- /dev/null +++ b/tests/components/bmi160/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_bmi160 + scl: 5 + sda: 4 + +sensor: + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature diff --git a/tests/components/bmi160/test.esp32-idf.yaml b/tests/components/bmi160/test.esp32-idf.yaml new file mode 100644 index 0000000000..a8a90c8c87 --- /dev/null +++ b/tests/components/bmi160/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_bmi160 + scl: 16 + sda: 17 + +sensor: + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature diff --git a/tests/components/bmi160/test.esp32.yaml b/tests/components/bmi160/test.esp32.yaml new file mode 100644 index 0000000000..a8a90c8c87 --- /dev/null +++ b/tests/components/bmi160/test.esp32.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_bmi160 + scl: 16 + sda: 17 + +sensor: + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature diff --git a/tests/components/bmi160/test.esp8266.yaml b/tests/components/bmi160/test.esp8266.yaml new file mode 100644 index 0000000000..3fd6441980 --- /dev/null +++ b/tests/components/bmi160/test.esp8266.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_bmi160 + scl: 5 + sda: 4 + +sensor: + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature diff --git a/tests/components/bmi160/test.rp2040.yaml b/tests/components/bmi160/test.rp2040.yaml new file mode 100644 index 0000000000..3fd6441980 --- /dev/null +++ b/tests/components/bmi160/test.rp2040.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_bmi160 + scl: 5 + sda: 4 + +sensor: + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature diff --git a/tests/components/bmp085/test.esp32-c3-idf.yaml b/tests/components/bmp085/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..76a9fd07ba --- /dev/null +++ b/tests/components/bmp085/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp085 + scl: 5 + sda: 4 + +sensor: + - platform: bmp085 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + filters: + - lambda: >- + return x / powf(1.0 - (x / 44330.0), 5.255); + update_interval: 15s diff --git a/tests/components/bmp085/test.esp32-c3.yaml b/tests/components/bmp085/test.esp32-c3.yaml new file mode 100644 index 0000000000..76a9fd07ba --- /dev/null +++ b/tests/components/bmp085/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp085 + scl: 5 + sda: 4 + +sensor: + - platform: bmp085 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + filters: + - lambda: >- + return x / powf(1.0 - (x / 44330.0), 5.255); + update_interval: 15s diff --git a/tests/components/bmp085/test.esp32-idf.yaml b/tests/components/bmp085/test.esp32-idf.yaml new file mode 100644 index 0000000000..8a4f714ddd --- /dev/null +++ b/tests/components/bmp085/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp085 + scl: 16 + sda: 17 + +sensor: + - platform: bmp085 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + filters: + - lambda: >- + return x / powf(1.0 - (x / 44330.0), 5.255); + update_interval: 15s diff --git a/tests/components/bmp085/test.esp32.yaml b/tests/components/bmp085/test.esp32.yaml new file mode 100644 index 0000000000..8a4f714ddd --- /dev/null +++ b/tests/components/bmp085/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp085 + scl: 16 + sda: 17 + +sensor: + - platform: bmp085 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + filters: + - lambda: >- + return x / powf(1.0 - (x / 44330.0), 5.255); + update_interval: 15s diff --git a/tests/components/bmp085/test.esp8266.yaml b/tests/components/bmp085/test.esp8266.yaml new file mode 100644 index 0000000000..76a9fd07ba --- /dev/null +++ b/tests/components/bmp085/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp085 + scl: 5 + sda: 4 + +sensor: + - platform: bmp085 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + filters: + - lambda: >- + return x / powf(1.0 - (x / 44330.0), 5.255); + update_interval: 15s diff --git a/tests/components/bmp085/test.rp2040.yaml b/tests/components/bmp085/test.rp2040.yaml new file mode 100644 index 0000000000..76a9fd07ba --- /dev/null +++ b/tests/components/bmp085/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp085 + scl: 5 + sda: 4 + +sensor: + - platform: bmp085 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + filters: + - lambda: >- + return x / powf(1.0 - (x / 44330.0), 5.255); + update_interval: 15s diff --git a/tests/components/bmp280/test.esp32-c3-idf.yaml b/tests/components/bmp280/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..5f7f85d3e2 --- /dev/null +++ b/tests/components/bmp280/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp280 + scl: 5 + sda: 4 + +sensor: + - platform: bmp280 + address: 0x77 + temperature: + name: Outside Temperature + oversampling: 16x + pressure: + name: Outside Pressure + iir_filter: 16x + update_interval: 15s diff --git a/tests/components/bmp280/test.esp32-c3.yaml b/tests/components/bmp280/test.esp32-c3.yaml new file mode 100644 index 0000000000..5f7f85d3e2 --- /dev/null +++ b/tests/components/bmp280/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp280 + scl: 5 + sda: 4 + +sensor: + - platform: bmp280 + address: 0x77 + temperature: + name: Outside Temperature + oversampling: 16x + pressure: + name: Outside Pressure + iir_filter: 16x + update_interval: 15s diff --git a/tests/components/bmp280/test.esp32-idf.yaml b/tests/components/bmp280/test.esp32-idf.yaml new file mode 100644 index 0000000000..aeb1cb262b --- /dev/null +++ b/tests/components/bmp280/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp280 + scl: 16 + sda: 17 + +sensor: + - platform: bmp280 + address: 0x77 + temperature: + name: Outside Temperature + oversampling: 16x + pressure: + name: Outside Pressure + iir_filter: 16x + update_interval: 15s diff --git a/tests/components/bmp280/test.esp32.yaml b/tests/components/bmp280/test.esp32.yaml new file mode 100644 index 0000000000..aeb1cb262b --- /dev/null +++ b/tests/components/bmp280/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp280 + scl: 16 + sda: 17 + +sensor: + - platform: bmp280 + address: 0x77 + temperature: + name: Outside Temperature + oversampling: 16x + pressure: + name: Outside Pressure + iir_filter: 16x + update_interval: 15s diff --git a/tests/components/bmp280/test.esp8266.yaml b/tests/components/bmp280/test.esp8266.yaml new file mode 100644 index 0000000000..5f7f85d3e2 --- /dev/null +++ b/tests/components/bmp280/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp280 + scl: 5 + sda: 4 + +sensor: + - platform: bmp280 + address: 0x77 + temperature: + name: Outside Temperature + oversampling: 16x + pressure: + name: Outside Pressure + iir_filter: 16x + update_interval: 15s diff --git a/tests/components/bmp280/test.rp2040.yaml b/tests/components/bmp280/test.rp2040.yaml new file mode 100644 index 0000000000..5f7f85d3e2 --- /dev/null +++ b/tests/components/bmp280/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp280 + scl: 5 + sda: 4 + +sensor: + - platform: bmp280 + address: 0x77 + temperature: + name: Outside Temperature + oversampling: 16x + pressure: + name: Outside Pressure + iir_filter: 16x + update_interval: 15s diff --git a/tests/components/bmp3xx/test.esp32-c3-idf.yaml b/tests/components/bmp3xx/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..3b244eccf9 --- /dev/null +++ b/tests/components/bmp3xx/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_bmp3xx + scl: 5 + sda: 4 + +sensor: + - platform: bmp3xx + address: 0x77 + temperature: + name: BMP Temperature + oversampling: 16x + pressure: + name: BMP Pressure + iir_filter: 2X diff --git a/tests/components/bmp3xx/test.esp32-c3.yaml b/tests/components/bmp3xx/test.esp32-c3.yaml new file mode 100644 index 0000000000..3b244eccf9 --- /dev/null +++ b/tests/components/bmp3xx/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_bmp3xx + scl: 5 + sda: 4 + +sensor: + - platform: bmp3xx + address: 0x77 + temperature: + name: BMP Temperature + oversampling: 16x + pressure: + name: BMP Pressure + iir_filter: 2X diff --git a/tests/components/bmp3xx/test.esp32-idf.yaml b/tests/components/bmp3xx/test.esp32-idf.yaml new file mode 100644 index 0000000000..677ed8a22d --- /dev/null +++ b/tests/components/bmp3xx/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_bmp3xx + scl: 16 + sda: 17 + +sensor: + - platform: bmp3xx + address: 0x77 + temperature: + name: BMP Temperature + oversampling: 16x + pressure: + name: BMP Pressure + iir_filter: 2X diff --git a/tests/components/bmp3xx/test.esp32.yaml b/tests/components/bmp3xx/test.esp32.yaml new file mode 100644 index 0000000000..677ed8a22d --- /dev/null +++ b/tests/components/bmp3xx/test.esp32.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_bmp3xx + scl: 16 + sda: 17 + +sensor: + - platform: bmp3xx + address: 0x77 + temperature: + name: BMP Temperature + oversampling: 16x + pressure: + name: BMP Pressure + iir_filter: 2X diff --git a/tests/components/bmp3xx/test.esp8266.yaml b/tests/components/bmp3xx/test.esp8266.yaml new file mode 100644 index 0000000000..3b244eccf9 --- /dev/null +++ b/tests/components/bmp3xx/test.esp8266.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_bmp3xx + scl: 5 + sda: 4 + +sensor: + - platform: bmp3xx + address: 0x77 + temperature: + name: BMP Temperature + oversampling: 16x + pressure: + name: BMP Pressure + iir_filter: 2X diff --git a/tests/components/bmp3xx/test.rp2040.yaml b/tests/components/bmp3xx/test.rp2040.yaml new file mode 100644 index 0000000000..3b244eccf9 --- /dev/null +++ b/tests/components/bmp3xx/test.rp2040.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_bmp3xx + scl: 5 + sda: 4 + +sensor: + - platform: bmp3xx + address: 0x77 + temperature: + name: BMP Temperature + oversampling: 16x + pressure: + name: BMP Pressure + iir_filter: 2X diff --git a/tests/components/bmp581/test.esp32-c3-idf.yaml b/tests/components/bmp581/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..29d27afb90 --- /dev/null +++ b/tests/components/bmp581/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_bmp581 + scl: 5 + sda: 4 + +sensor: + - platform: bmp581 + temperature: + name: "BMP581 Temperature" + iir_filter: 2x + pressure: + name: "BMP581 Pressure" + oversampling: 128x diff --git a/tests/components/bmp581/test.esp32-c3.yaml b/tests/components/bmp581/test.esp32-c3.yaml new file mode 100644 index 0000000000..29d27afb90 --- /dev/null +++ b/tests/components/bmp581/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_bmp581 + scl: 5 + sda: 4 + +sensor: + - platform: bmp581 + temperature: + name: "BMP581 Temperature" + iir_filter: 2x + pressure: + name: "BMP581 Pressure" + oversampling: 128x diff --git a/tests/components/bmp581/test.esp32-idf.yaml b/tests/components/bmp581/test.esp32-idf.yaml new file mode 100644 index 0000000000..a464b8ce6a --- /dev/null +++ b/tests/components/bmp581/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_bmp581 + scl: 16 + sda: 17 + +sensor: + - platform: bmp581 + temperature: + name: "BMP581 Temperature" + iir_filter: 2x + pressure: + name: "BMP581 Pressure" + oversampling: 128x diff --git a/tests/components/bmp581/test.esp32.yaml b/tests/components/bmp581/test.esp32.yaml new file mode 100644 index 0000000000..a464b8ce6a --- /dev/null +++ b/tests/components/bmp581/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_bmp581 + scl: 16 + sda: 17 + +sensor: + - platform: bmp581 + temperature: + name: "BMP581 Temperature" + iir_filter: 2x + pressure: + name: "BMP581 Pressure" + oversampling: 128x diff --git a/tests/components/bmp581/test.esp8266.yaml b/tests/components/bmp581/test.esp8266.yaml new file mode 100644 index 0000000000..29d27afb90 --- /dev/null +++ b/tests/components/bmp581/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_bmp581 + scl: 5 + sda: 4 + +sensor: + - platform: bmp581 + temperature: + name: "BMP581 Temperature" + iir_filter: 2x + pressure: + name: "BMP581 Pressure" + oversampling: 128x diff --git a/tests/components/bmp581/test.rp2040.yaml b/tests/components/bmp581/test.rp2040.yaml new file mode 100644 index 0000000000..29d27afb90 --- /dev/null +++ b/tests/components/bmp581/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_bmp581 + scl: 5 + sda: 4 + +sensor: + - platform: bmp581 + temperature: + name: "BMP581 Temperature" + iir_filter: 2x + pressure: + name: "BMP581 Pressure" + oversampling: 128x diff --git a/tests/components/bp1658cj/test.esp32-c3-idf.yaml b/tests/components/bp1658cj/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..74d3155371 --- /dev/null +++ b/tests/components/bp1658cj/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +bp1658cj: + clock_pin: 5 + data_pin: 4 + max_power_color_channels: 4 + max_power_white_channels: 6 + +output: + - platform: bp1658cj + id: bp1658cj_red + channel: 1 + - platform: bp1658cj + id: bp1658cj_green + channel: 2 + - platform: bp1658cj + id: bp1658cj_blue + channel: 0 + - platform: bp1658cj + id: bp1658cj_coldwhite + channel: 3 + - platform: bp1658cj + id: bp1658cj_warmwhite + channel: 4 diff --git a/tests/components/bp1658cj/test.esp32-c3.yaml b/tests/components/bp1658cj/test.esp32-c3.yaml new file mode 100644 index 0000000000..74d3155371 --- /dev/null +++ b/tests/components/bp1658cj/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +bp1658cj: + clock_pin: 5 + data_pin: 4 + max_power_color_channels: 4 + max_power_white_channels: 6 + +output: + - platform: bp1658cj + id: bp1658cj_red + channel: 1 + - platform: bp1658cj + id: bp1658cj_green + channel: 2 + - platform: bp1658cj + id: bp1658cj_blue + channel: 0 + - platform: bp1658cj + id: bp1658cj_coldwhite + channel: 3 + - platform: bp1658cj + id: bp1658cj_warmwhite + channel: 4 diff --git a/tests/components/bp1658cj/test.esp32-idf.yaml b/tests/components/bp1658cj/test.esp32-idf.yaml new file mode 100644 index 0000000000..5f9e25d3bd --- /dev/null +++ b/tests/components/bp1658cj/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +bp1658cj: + clock_pin: 16 + data_pin: 17 + max_power_color_channels: 4 + max_power_white_channels: 6 + +output: + - platform: bp1658cj + id: bp1658cj_red + channel: 1 + - platform: bp1658cj + id: bp1658cj_green + channel: 2 + - platform: bp1658cj + id: bp1658cj_blue + channel: 0 + - platform: bp1658cj + id: bp1658cj_coldwhite + channel: 3 + - platform: bp1658cj + id: bp1658cj_warmwhite + channel: 4 diff --git a/tests/components/bp1658cj/test.esp32.yaml b/tests/components/bp1658cj/test.esp32.yaml new file mode 100644 index 0000000000..5f9e25d3bd --- /dev/null +++ b/tests/components/bp1658cj/test.esp32.yaml @@ -0,0 +1,22 @@ +bp1658cj: + clock_pin: 16 + data_pin: 17 + max_power_color_channels: 4 + max_power_white_channels: 6 + +output: + - platform: bp1658cj + id: bp1658cj_red + channel: 1 + - platform: bp1658cj + id: bp1658cj_green + channel: 2 + - platform: bp1658cj + id: bp1658cj_blue + channel: 0 + - platform: bp1658cj + id: bp1658cj_coldwhite + channel: 3 + - platform: bp1658cj + id: bp1658cj_warmwhite + channel: 4 diff --git a/tests/components/bp1658cj/test.esp8266.yaml b/tests/components/bp1658cj/test.esp8266.yaml new file mode 100644 index 0000000000..74d3155371 --- /dev/null +++ b/tests/components/bp1658cj/test.esp8266.yaml @@ -0,0 +1,22 @@ +bp1658cj: + clock_pin: 5 + data_pin: 4 + max_power_color_channels: 4 + max_power_white_channels: 6 + +output: + - platform: bp1658cj + id: bp1658cj_red + channel: 1 + - platform: bp1658cj + id: bp1658cj_green + channel: 2 + - platform: bp1658cj + id: bp1658cj_blue + channel: 0 + - platform: bp1658cj + id: bp1658cj_coldwhite + channel: 3 + - platform: bp1658cj + id: bp1658cj_warmwhite + channel: 4 diff --git a/tests/components/bp1658cj/test.rp2040.yaml b/tests/components/bp1658cj/test.rp2040.yaml new file mode 100644 index 0000000000..74d3155371 --- /dev/null +++ b/tests/components/bp1658cj/test.rp2040.yaml @@ -0,0 +1,22 @@ +bp1658cj: + clock_pin: 5 + data_pin: 4 + max_power_color_channels: 4 + max_power_white_channels: 6 + +output: + - platform: bp1658cj + id: bp1658cj_red + channel: 1 + - platform: bp1658cj + id: bp1658cj_green + channel: 2 + - platform: bp1658cj + id: bp1658cj_blue + channel: 0 + - platform: bp1658cj + id: bp1658cj_coldwhite + channel: 3 + - platform: bp1658cj + id: bp1658cj_warmwhite + channel: 4 diff --git a/tests/components/bp5758d/test.esp32-c3-idf.yaml b/tests/components/bp5758d/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ec74e935cd --- /dev/null +++ b/tests/components/bp5758d/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +bp5758d: + clock_pin: 5 + data_pin: 4 + +output: + - platform: bp5758d + id: bp5758d_red + channel: 2 + current: 10 + - platform: bp5758d + id: bp5758d_green + channel: 3 + current: 10 + - platform: bp5758d + id: bp5758d_blue + channel: 1 + current: 10 + - platform: bp5758d + id: bp5758d_coldwhite + channel: 5 + current: 10 + - platform: bp5758d + id: bp5758d_warmwhite + channel: 4 + current: 10 diff --git a/tests/components/bp5758d/test.esp32-c3.yaml b/tests/components/bp5758d/test.esp32-c3.yaml new file mode 100644 index 0000000000..ec74e935cd --- /dev/null +++ b/tests/components/bp5758d/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +bp5758d: + clock_pin: 5 + data_pin: 4 + +output: + - platform: bp5758d + id: bp5758d_red + channel: 2 + current: 10 + - platform: bp5758d + id: bp5758d_green + channel: 3 + current: 10 + - platform: bp5758d + id: bp5758d_blue + channel: 1 + current: 10 + - platform: bp5758d + id: bp5758d_coldwhite + channel: 5 + current: 10 + - platform: bp5758d + id: bp5758d_warmwhite + channel: 4 + current: 10 diff --git a/tests/components/bp5758d/test.esp32-idf.yaml b/tests/components/bp5758d/test.esp32-idf.yaml new file mode 100644 index 0000000000..b7929a0518 --- /dev/null +++ b/tests/components/bp5758d/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +bp5758d: + clock_pin: 16 + data_pin: 17 + +output: + - platform: bp5758d + id: bp5758d_red + channel: 2 + current: 10 + - platform: bp5758d + id: bp5758d_green + channel: 3 + current: 10 + - platform: bp5758d + id: bp5758d_blue + channel: 1 + current: 10 + - platform: bp5758d + id: bp5758d_coldwhite + channel: 5 + current: 10 + - platform: bp5758d + id: bp5758d_warmwhite + channel: 4 + current: 10 diff --git a/tests/components/bp5758d/test.esp32.yaml b/tests/components/bp5758d/test.esp32.yaml new file mode 100644 index 0000000000..b7929a0518 --- /dev/null +++ b/tests/components/bp5758d/test.esp32.yaml @@ -0,0 +1,25 @@ +bp5758d: + clock_pin: 16 + data_pin: 17 + +output: + - platform: bp5758d + id: bp5758d_red + channel: 2 + current: 10 + - platform: bp5758d + id: bp5758d_green + channel: 3 + current: 10 + - platform: bp5758d + id: bp5758d_blue + channel: 1 + current: 10 + - platform: bp5758d + id: bp5758d_coldwhite + channel: 5 + current: 10 + - platform: bp5758d + id: bp5758d_warmwhite + channel: 4 + current: 10 diff --git a/tests/components/bp5758d/test.esp8266.yaml b/tests/components/bp5758d/test.esp8266.yaml new file mode 100644 index 0000000000..ec74e935cd --- /dev/null +++ b/tests/components/bp5758d/test.esp8266.yaml @@ -0,0 +1,25 @@ +bp5758d: + clock_pin: 5 + data_pin: 4 + +output: + - platform: bp5758d + id: bp5758d_red + channel: 2 + current: 10 + - platform: bp5758d + id: bp5758d_green + channel: 3 + current: 10 + - platform: bp5758d + id: bp5758d_blue + channel: 1 + current: 10 + - platform: bp5758d + id: bp5758d_coldwhite + channel: 5 + current: 10 + - platform: bp5758d + id: bp5758d_warmwhite + channel: 4 + current: 10 diff --git a/tests/components/bp5758d/test.rp2040.yaml b/tests/components/bp5758d/test.rp2040.yaml new file mode 100644 index 0000000000..ec74e935cd --- /dev/null +++ b/tests/components/bp5758d/test.rp2040.yaml @@ -0,0 +1,25 @@ +bp5758d: + clock_pin: 5 + data_pin: 4 + +output: + - platform: bp5758d + id: bp5758d_red + channel: 2 + current: 10 + - platform: bp5758d + id: bp5758d_green + channel: 3 + current: 10 + - platform: bp5758d + id: bp5758d_blue + channel: 1 + current: 10 + - platform: bp5758d + id: bp5758d_coldwhite + channel: 5 + current: 10 + - platform: bp5758d + id: bp5758d_warmwhite + channel: 4 + current: 10 diff --git a/tests/components/button/test.esp32-c3-idf.yaml b/tests/components/button/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..d5978601f4 --- /dev/null +++ b/tests/components/button/test.esp32-c3-idf.yaml @@ -0,0 +1,6 @@ +button: + - platform: template + name: Button + id: some_button + on_press: + - logger.log: Button pressed diff --git a/tests/components/button/test.esp32-c3.yaml b/tests/components/button/test.esp32-c3.yaml new file mode 100644 index 0000000000..d5978601f4 --- /dev/null +++ b/tests/components/button/test.esp32-c3.yaml @@ -0,0 +1,6 @@ +button: + - platform: template + name: Button + id: some_button + on_press: + - logger.log: Button pressed diff --git a/tests/components/button/test.esp32-idf.yaml b/tests/components/button/test.esp32-idf.yaml new file mode 100644 index 0000000000..d5978601f4 --- /dev/null +++ b/tests/components/button/test.esp32-idf.yaml @@ -0,0 +1,6 @@ +button: + - platform: template + name: Button + id: some_button + on_press: + - logger.log: Button pressed diff --git a/tests/components/button/test.esp32.yaml b/tests/components/button/test.esp32.yaml new file mode 100644 index 0000000000..d5978601f4 --- /dev/null +++ b/tests/components/button/test.esp32.yaml @@ -0,0 +1,6 @@ +button: + - platform: template + name: Button + id: some_button + on_press: + - logger.log: Button pressed diff --git a/tests/components/button/test.esp8266.yaml b/tests/components/button/test.esp8266.yaml new file mode 100644 index 0000000000..d5978601f4 --- /dev/null +++ b/tests/components/button/test.esp8266.yaml @@ -0,0 +1,6 @@ +button: + - platform: template + name: Button + id: some_button + on_press: + - logger.log: Button pressed diff --git a/tests/components/button/test.rp2040.yaml b/tests/components/button/test.rp2040.yaml new file mode 100644 index 0000000000..d5978601f4 --- /dev/null +++ b/tests/components/button/test.rp2040.yaml @@ -0,0 +1,6 @@ +button: + - platform: template + name: Button + id: some_button + on_press: + - logger.log: Button pressed diff --git a/tests/components/canbus/test.esp32-c3-idf.yaml b/tests/components/canbus/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..fd146cc3a3 --- /dev/null +++ b/tests/components/canbus/test.esp32-c3-idf.yaml @@ -0,0 +1,46 @@ +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: 4 + tx_pin: 5 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", b.c_str()); + - can_id: 23 + then: + - if: + condition: + lambda: "return x[0] == 0x11;" + then: + logger.log: Truth + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + } + +button: + - platform: template + name: Canbus Actions + on_press: + - canbus.send: "abc" + - canbus.send: [0, 1, 2] + - canbus.send: !lambda return {0, 1, 2}; diff --git a/tests/components/canbus/test.esp32-c3.yaml b/tests/components/canbus/test.esp32-c3.yaml new file mode 100644 index 0000000000..fd146cc3a3 --- /dev/null +++ b/tests/components/canbus/test.esp32-c3.yaml @@ -0,0 +1,46 @@ +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: 4 + tx_pin: 5 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", b.c_str()); + - can_id: 23 + then: + - if: + condition: + lambda: "return x[0] == 0x11;" + then: + logger.log: Truth + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + } + +button: + - platform: template + name: Canbus Actions + on_press: + - canbus.send: "abc" + - canbus.send: [0, 1, 2] + - canbus.send: !lambda return {0, 1, 2}; diff --git a/tests/components/canbus/test.esp32-idf.yaml b/tests/components/canbus/test.esp32-idf.yaml new file mode 100644 index 0000000000..fd146cc3a3 --- /dev/null +++ b/tests/components/canbus/test.esp32-idf.yaml @@ -0,0 +1,46 @@ +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: 4 + tx_pin: 5 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", b.c_str()); + - can_id: 23 + then: + - if: + condition: + lambda: "return x[0] == 0x11;" + then: + logger.log: Truth + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + } + +button: + - platform: template + name: Canbus Actions + on_press: + - canbus.send: "abc" + - canbus.send: [0, 1, 2] + - canbus.send: !lambda return {0, 1, 2}; diff --git a/tests/components/canbus/test.esp32.yaml b/tests/components/canbus/test.esp32.yaml new file mode 100644 index 0000000000..fd146cc3a3 --- /dev/null +++ b/tests/components/canbus/test.esp32.yaml @@ -0,0 +1,46 @@ +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: 4 + tx_pin: 5 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", b.c_str()); + - can_id: 23 + then: + - if: + condition: + lambda: "return x[0] == 0x11;" + then: + logger.log: Truth + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + } + +button: + - platform: template + name: Canbus Actions + on_press: + - canbus.send: "abc" + - canbus.send: [0, 1, 2] + - canbus.send: !lambda return {0, 1, 2}; diff --git a/tests/components/cap1188/test.esp32-c3-idf.yaml b/tests/components/cap1188/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..c6d3c95942 --- /dev/null +++ b/tests/components/cap1188/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_cap1188 + scl: 5 + sda: 4 + +cap1188: + id: cap1188_component + address: 0x29 + reset_pin: 6 + touch_threshold: 0x20 + allow_multiple_touches: true diff --git a/tests/components/cap1188/test.esp32-c3.yaml b/tests/components/cap1188/test.esp32-c3.yaml new file mode 100644 index 0000000000..c6d3c95942 --- /dev/null +++ b/tests/components/cap1188/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_cap1188 + scl: 5 + sda: 4 + +cap1188: + id: cap1188_component + address: 0x29 + reset_pin: 6 + touch_threshold: 0x20 + allow_multiple_touches: true diff --git a/tests/components/cap1188/test.esp32-idf.yaml b/tests/components/cap1188/test.esp32-idf.yaml new file mode 100644 index 0000000000..efd1d60217 --- /dev/null +++ b/tests/components/cap1188/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_cap1188 + scl: 16 + sda: 17 + +cap1188: + id: cap1188_component + address: 0x29 + reset_pin: 15 + touch_threshold: 0x20 + allow_multiple_touches: true diff --git a/tests/components/cap1188/test.esp32.yaml b/tests/components/cap1188/test.esp32.yaml new file mode 100644 index 0000000000..efd1d60217 --- /dev/null +++ b/tests/components/cap1188/test.esp32.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_cap1188 + scl: 16 + sda: 17 + +cap1188: + id: cap1188_component + address: 0x29 + reset_pin: 15 + touch_threshold: 0x20 + allow_multiple_touches: true diff --git a/tests/components/cap1188/test.esp8266.yaml b/tests/components/cap1188/test.esp8266.yaml new file mode 100644 index 0000000000..7573d45140 --- /dev/null +++ b/tests/components/cap1188/test.esp8266.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_cap1188 + scl: 5 + sda: 4 + +cap1188: + id: cap1188_component + address: 0x29 + reset_pin: 15 + touch_threshold: 0x20 + allow_multiple_touches: true diff --git a/tests/components/cap1188/test.rp2040.yaml b/tests/components/cap1188/test.rp2040.yaml new file mode 100644 index 0000000000..c6d3c95942 --- /dev/null +++ b/tests/components/cap1188/test.rp2040.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_cap1188 + scl: 5 + sda: 4 + +cap1188: + id: cap1188_component + address: 0x29 + reset_pin: 6 + touch_threshold: 0x20 + allow_multiple_touches: true diff --git a/tests/components/captive_portal/test.esp32-c3-idf.yaml b/tests/components/captive_portal/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..25bc4a887a --- /dev/null +++ b/tests/components/captive_portal/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +wifi: + ssid: MySSID + password: password1 + +captive_portal: diff --git a/tests/components/captive_portal/test.esp32-c3.yaml b/tests/components/captive_portal/test.esp32-c3.yaml new file mode 100644 index 0000000000..25bc4a887a --- /dev/null +++ b/tests/components/captive_portal/test.esp32-c3.yaml @@ -0,0 +1,5 @@ +wifi: + ssid: MySSID + password: password1 + +captive_portal: diff --git a/tests/components/captive_portal/test.esp32-idf.yaml b/tests/components/captive_portal/test.esp32-idf.yaml new file mode 100644 index 0000000000..25bc4a887a --- /dev/null +++ b/tests/components/captive_portal/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +wifi: + ssid: MySSID + password: password1 + +captive_portal: diff --git a/tests/components/captive_portal/test.esp32.yaml b/tests/components/captive_portal/test.esp32.yaml new file mode 100644 index 0000000000..25bc4a887a --- /dev/null +++ b/tests/components/captive_portal/test.esp32.yaml @@ -0,0 +1,5 @@ +wifi: + ssid: MySSID + password: password1 + +captive_portal: diff --git a/tests/components/captive_portal/test.esp8266.yaml b/tests/components/captive_portal/test.esp8266.yaml new file mode 100644 index 0000000000..25bc4a887a --- /dev/null +++ b/tests/components/captive_portal/test.esp8266.yaml @@ -0,0 +1,5 @@ +wifi: + ssid: MySSID + password: password1 + +captive_portal: diff --git a/tests/components/ccs811/test.esp32-c3-idf.yaml b/tests/components/ccs811/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..26ec7807e4 --- /dev/null +++ b/tests/components/ccs811/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ccs811 + scl: 5 + sda: 4 + +sensor: + - platform: ccs811 + eco2: + name: CCS811 eCO2 + tvoc: + name: CCS811 TVOC + baseline: 0x4242 + update_interval: 30s diff --git a/tests/components/ccs811/test.esp32-c3.yaml b/tests/components/ccs811/test.esp32-c3.yaml new file mode 100644 index 0000000000..26ec7807e4 --- /dev/null +++ b/tests/components/ccs811/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ccs811 + scl: 5 + sda: 4 + +sensor: + - platform: ccs811 + eco2: + name: CCS811 eCO2 + tvoc: + name: CCS811 TVOC + baseline: 0x4242 + update_interval: 30s diff --git a/tests/components/ccs811/test.esp32-idf.yaml b/tests/components/ccs811/test.esp32-idf.yaml new file mode 100644 index 0000000000..08b3a48cc7 --- /dev/null +++ b/tests/components/ccs811/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ccs811 + scl: 16 + sda: 17 + +sensor: + - platform: ccs811 + eco2: + name: CCS811 eCO2 + tvoc: + name: CCS811 TVOC + baseline: 0x4242 + update_interval: 30s diff --git a/tests/components/ccs811/test.esp32.yaml b/tests/components/ccs811/test.esp32.yaml new file mode 100644 index 0000000000..08b3a48cc7 --- /dev/null +++ b/tests/components/ccs811/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ccs811 + scl: 16 + sda: 17 + +sensor: + - platform: ccs811 + eco2: + name: CCS811 eCO2 + tvoc: + name: CCS811 TVOC + baseline: 0x4242 + update_interval: 30s diff --git a/tests/components/ccs811/test.esp8266.yaml b/tests/components/ccs811/test.esp8266.yaml new file mode 100644 index 0000000000..26ec7807e4 --- /dev/null +++ b/tests/components/ccs811/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ccs811 + scl: 5 + sda: 4 + +sensor: + - platform: ccs811 + eco2: + name: CCS811 eCO2 + tvoc: + name: CCS811 TVOC + baseline: 0x4242 + update_interval: 30s diff --git a/tests/components/ccs811/test.rp2040.yaml b/tests/components/ccs811/test.rp2040.yaml new file mode 100644 index 0000000000..26ec7807e4 --- /dev/null +++ b/tests/components/ccs811/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ccs811 + scl: 5 + sda: 4 + +sensor: + - platform: ccs811 + eco2: + name: CCS811 eCO2 + tvoc: + name: CCS811 TVOC + baseline: 0x4242 + update_interval: 30s diff --git a/tests/components/cd74hc4067/test.esp32-c3-idf.yaml b/tests/components/cd74hc4067/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..5aa653d26c --- /dev/null +++ b/tests/components/cd74hc4067/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +cd74hc4067: + pin_s0: 2 + pin_s1: 3 + pin_s2: 4 + pin_s3: 5 + +sensor: + - platform: adc + id: esp_adc_sensor + pin: 0 + - platform: cd74hc4067 + id: cd74hc4067_adc_0 + number: 0 + sensor: esp_adc_sensor + - platform: cd74hc4067 + id: cd74hc4067_adc_1 + number: 1 + sensor: esp_adc_sensor diff --git a/tests/components/cd74hc4067/test.esp32-c3.yaml b/tests/components/cd74hc4067/test.esp32-c3.yaml new file mode 100644 index 0000000000..5aa653d26c --- /dev/null +++ b/tests/components/cd74hc4067/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +cd74hc4067: + pin_s0: 2 + pin_s1: 3 + pin_s2: 4 + pin_s3: 5 + +sensor: + - platform: adc + id: esp_adc_sensor + pin: 0 + - platform: cd74hc4067 + id: cd74hc4067_adc_0 + number: 0 + sensor: esp_adc_sensor + - platform: cd74hc4067 + id: cd74hc4067_adc_1 + number: 1 + sensor: esp_adc_sensor diff --git a/tests/components/cd74hc4067/test.esp32-idf.yaml b/tests/components/cd74hc4067/test.esp32-idf.yaml new file mode 100644 index 0000000000..71a1238ccc --- /dev/null +++ b/tests/components/cd74hc4067/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +cd74hc4067: + pin_s0: 12 + pin_s1: 13 + pin_s2: 14 + pin_s3: 15 + +sensor: + - platform: adc + id: esp_adc_sensor + pin: 39 + - platform: cd74hc4067 + id: cd74hc4067_adc_0 + number: 0 + sensor: esp_adc_sensor + - platform: cd74hc4067 + id: cd74hc4067_adc_1 + number: 1 + sensor: esp_adc_sensor diff --git a/tests/components/cd74hc4067/test.esp32.yaml b/tests/components/cd74hc4067/test.esp32.yaml new file mode 100644 index 0000000000..71a1238ccc --- /dev/null +++ b/tests/components/cd74hc4067/test.esp32.yaml @@ -0,0 +1,18 @@ +cd74hc4067: + pin_s0: 12 + pin_s1: 13 + pin_s2: 14 + pin_s3: 15 + +sensor: + - platform: adc + id: esp_adc_sensor + pin: 39 + - platform: cd74hc4067 + id: cd74hc4067_adc_0 + number: 0 + sensor: esp_adc_sensor + - platform: cd74hc4067 + id: cd74hc4067_adc_1 + number: 1 + sensor: esp_adc_sensor diff --git a/tests/components/cd74hc4067/test.esp8266.yaml b/tests/components/cd74hc4067/test.esp8266.yaml new file mode 100644 index 0000000000..8bcce5bc17 --- /dev/null +++ b/tests/components/cd74hc4067/test.esp8266.yaml @@ -0,0 +1,18 @@ +cd74hc4067: + pin_s0: 12 + pin_s1: 13 + pin_s2: 14 + pin_s3: 15 + +sensor: + - platform: adc + id: esp_adc_sensor + pin: A0 + - platform: cd74hc4067 + id: cd74hc4067_adc_0 + number: 0 + sensor: esp_adc_sensor + - platform: cd74hc4067 + id: cd74hc4067_adc_1 + number: 1 + sensor: esp_adc_sensor diff --git a/tests/components/cd74hc4067/test.rp2040.yaml b/tests/components/cd74hc4067/test.rp2040.yaml new file mode 100644 index 0000000000..75adcce796 --- /dev/null +++ b/tests/components/cd74hc4067/test.rp2040.yaml @@ -0,0 +1,18 @@ +cd74hc4067: + pin_s0: 2 + pin_s1: 3 + pin_s2: 4 + pin_s3: 5 + +sensor: + - platform: adc + id: esp_adc_sensor + pin: 26 + - platform: cd74hc4067 + id: cd74hc4067_adc_0 + number: 0 + sensor: esp_adc_sensor + - platform: cd74hc4067 + id: cd74hc4067_adc_1 + number: 1 + sensor: esp_adc_sensor diff --git a/tests/components/climate_ir_lg/test.esp32-c3-idf.yaml b/tests/components/climate_ir_lg/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..e714bf0686 --- /dev/null +++ b/tests/components/climate_ir_lg/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: climate_ir_lg + name: LG Climate diff --git a/tests/components/climate_ir_lg/test.esp32-c3.yaml b/tests/components/climate_ir_lg/test.esp32-c3.yaml new file mode 100644 index 0000000000..e714bf0686 --- /dev/null +++ b/tests/components/climate_ir_lg/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: climate_ir_lg + name: LG Climate diff --git a/tests/components/climate_ir_lg/test.esp32-idf.yaml b/tests/components/climate_ir_lg/test.esp32-idf.yaml new file mode 100644 index 0000000000..e714bf0686 --- /dev/null +++ b/tests/components/climate_ir_lg/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: climate_ir_lg + name: LG Climate diff --git a/tests/components/climate_ir_lg/test.esp32.yaml b/tests/components/climate_ir_lg/test.esp32.yaml new file mode 100644 index 0000000000..e714bf0686 --- /dev/null +++ b/tests/components/climate_ir_lg/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: climate_ir_lg + name: LG Climate diff --git a/tests/components/climate_ir_lg/test.esp8266.yaml b/tests/components/climate_ir_lg/test.esp8266.yaml new file mode 100644 index 0000000000..7482bf0580 --- /dev/null +++ b/tests/components/climate_ir_lg/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: climate_ir_lg + name: LG Climate diff --git a/tests/components/color/test.esp32-c3-idf.yaml b/tests/components/color/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..7aa308bb63 --- /dev/null +++ b/tests/components/color/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +color: + - id: kbx_red + red: 100% + green_int: 123 + blue: 2% + - id: kbx_blue + red: 0% + green: 1% + blue: 100% + - id: kbx_green + hex: "3DEC55" diff --git a/tests/components/color/test.esp32-c3.yaml b/tests/components/color/test.esp32-c3.yaml new file mode 100644 index 0000000000..7aa308bb63 --- /dev/null +++ b/tests/components/color/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +color: + - id: kbx_red + red: 100% + green_int: 123 + blue: 2% + - id: kbx_blue + red: 0% + green: 1% + blue: 100% + - id: kbx_green + hex: "3DEC55" diff --git a/tests/components/color/test.esp32-idf.yaml b/tests/components/color/test.esp32-idf.yaml new file mode 100644 index 0000000000..7aa308bb63 --- /dev/null +++ b/tests/components/color/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +color: + - id: kbx_red + red: 100% + green_int: 123 + blue: 2% + - id: kbx_blue + red: 0% + green: 1% + blue: 100% + - id: kbx_green + hex: "3DEC55" diff --git a/tests/components/color/test.esp32.yaml b/tests/components/color/test.esp32.yaml new file mode 100644 index 0000000000..7aa308bb63 --- /dev/null +++ b/tests/components/color/test.esp32.yaml @@ -0,0 +1,11 @@ +color: + - id: kbx_red + red: 100% + green_int: 123 + blue: 2% + - id: kbx_blue + red: 0% + green: 1% + blue: 100% + - id: kbx_green + hex: "3DEC55" diff --git a/tests/components/color/test.esp8266.yaml b/tests/components/color/test.esp8266.yaml new file mode 100644 index 0000000000..7aa308bb63 --- /dev/null +++ b/tests/components/color/test.esp8266.yaml @@ -0,0 +1,11 @@ +color: + - id: kbx_red + red: 100% + green_int: 123 + blue: 2% + - id: kbx_blue + red: 0% + green: 1% + blue: 100% + - id: kbx_green + hex: "3DEC55" diff --git a/tests/components/color/test.rp2040.yaml b/tests/components/color/test.rp2040.yaml new file mode 100644 index 0000000000..7aa308bb63 --- /dev/null +++ b/tests/components/color/test.rp2040.yaml @@ -0,0 +1,11 @@ +color: + - id: kbx_red + red: 100% + green_int: 123 + blue: 2% + - id: kbx_blue + red: 0% + green: 1% + blue: 100% + - id: kbx_green + hex: "3DEC55" diff --git a/tests/components/color_temperature/test.esp32-c3-idf.yaml b/tests/components/color_temperature/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..8d3faa54ee --- /dev/null +++ b/tests/components/color_temperature/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + +light: + - platform: color_temperature + name: Lights + color_temperature: light_output_1 + brightness: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/color_temperature/test.esp32-c3.yaml b/tests/components/color_temperature/test.esp32-c3.yaml new file mode 100644 index 0000000000..8d3faa54ee --- /dev/null +++ b/tests/components/color_temperature/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + +light: + - platform: color_temperature + name: Lights + color_temperature: light_output_1 + brightness: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/color_temperature/test.esp32-idf.yaml b/tests/components/color_temperature/test.esp32-idf.yaml new file mode 100644 index 0000000000..608907d2fc --- /dev/null +++ b/tests/components/color_temperature/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + +light: + - platform: color_temperature + name: Lights + color_temperature: light_output_1 + brightness: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/color_temperature/test.esp32.yaml b/tests/components/color_temperature/test.esp32.yaml new file mode 100644 index 0000000000..608907d2fc --- /dev/null +++ b/tests/components/color_temperature/test.esp32.yaml @@ -0,0 +1,15 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + +light: + - platform: color_temperature + name: Lights + color_temperature: light_output_1 + brightness: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/color_temperature/test.esp8266.yaml b/tests/components/color_temperature/test.esp8266.yaml new file mode 100644 index 0000000000..ed0bfb6aa4 --- /dev/null +++ b/tests/components/color_temperature/test.esp8266.yaml @@ -0,0 +1,15 @@ +output: + - platform: esp8266_pwm + id: light_output_1 + pin: 12 + - platform: esp8266_pwm + id: light_output_2 + pin: 13 + +light: + - platform: color_temperature + name: Lights + color_temperature: light_output_1 + brightness: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/color_temperature/test.rp2040.yaml b/tests/components/color_temperature/test.rp2040.yaml new file mode 100644 index 0000000000..887ad1c857 --- /dev/null +++ b/tests/components/color_temperature/test.rp2040.yaml @@ -0,0 +1,15 @@ +output: + - platform: rp2040_pwm + id: light_output_1 + pin: 12 + - platform: rp2040_pwm + id: light_output_2 + pin: 13 + +light: + - platform: color_temperature + name: Lights + color_temperature: light_output_1 + brightness: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/combination/test.esp32-c3-idf.yaml b/tests/components/combination/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..62246190af --- /dev/null +++ b/tests/components/combination/test.esp32-c3-idf.yaml @@ -0,0 +1,76 @@ +sensor: + - platform: template + id: template_temperature1 + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature2 + lambda: |- + if (millis() > 20000) { + return 0.8; + } else { + return 0.0; + } + - platform: combination + type: kalman + name: Kalman-filtered temperature + process_std_dev: 0.00139 + sources: + - source: template_temperature1 + error: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + error: 1.5 + - platform: combination + type: linear + name: Linearly combined temperatures + sources: + - source: template_temperature1 + coeffecient: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + coeffecient: 1.5 + - platform: combination + type: max + name: Max of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: mean + name: Mean of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: median + name: Median of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: min + name: Min of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: most_recently_updated + name: Most recently updated of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: range + name: Range of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: sum + name: Sum of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 diff --git a/tests/components/combination/test.esp32-c3.yaml b/tests/components/combination/test.esp32-c3.yaml new file mode 100644 index 0000000000..62246190af --- /dev/null +++ b/tests/components/combination/test.esp32-c3.yaml @@ -0,0 +1,76 @@ +sensor: + - platform: template + id: template_temperature1 + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature2 + lambda: |- + if (millis() > 20000) { + return 0.8; + } else { + return 0.0; + } + - platform: combination + type: kalman + name: Kalman-filtered temperature + process_std_dev: 0.00139 + sources: + - source: template_temperature1 + error: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + error: 1.5 + - platform: combination + type: linear + name: Linearly combined temperatures + sources: + - source: template_temperature1 + coeffecient: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + coeffecient: 1.5 + - platform: combination + type: max + name: Max of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: mean + name: Mean of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: median + name: Median of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: min + name: Min of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: most_recently_updated + name: Most recently updated of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: range + name: Range of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: sum + name: Sum of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 diff --git a/tests/components/combination/test.esp32-idf.yaml b/tests/components/combination/test.esp32-idf.yaml new file mode 100644 index 0000000000..62246190af --- /dev/null +++ b/tests/components/combination/test.esp32-idf.yaml @@ -0,0 +1,76 @@ +sensor: + - platform: template + id: template_temperature1 + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature2 + lambda: |- + if (millis() > 20000) { + return 0.8; + } else { + return 0.0; + } + - platform: combination + type: kalman + name: Kalman-filtered temperature + process_std_dev: 0.00139 + sources: + - source: template_temperature1 + error: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + error: 1.5 + - platform: combination + type: linear + name: Linearly combined temperatures + sources: + - source: template_temperature1 + coeffecient: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + coeffecient: 1.5 + - platform: combination + type: max + name: Max of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: mean + name: Mean of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: median + name: Median of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: min + name: Min of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: most_recently_updated + name: Most recently updated of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: range + name: Range of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: sum + name: Sum of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 diff --git a/tests/components/combination/test.esp32.yaml b/tests/components/combination/test.esp32.yaml new file mode 100644 index 0000000000..62246190af --- /dev/null +++ b/tests/components/combination/test.esp32.yaml @@ -0,0 +1,76 @@ +sensor: + - platform: template + id: template_temperature1 + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature2 + lambda: |- + if (millis() > 20000) { + return 0.8; + } else { + return 0.0; + } + - platform: combination + type: kalman + name: Kalman-filtered temperature + process_std_dev: 0.00139 + sources: + - source: template_temperature1 + error: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + error: 1.5 + - platform: combination + type: linear + name: Linearly combined temperatures + sources: + - source: template_temperature1 + coeffecient: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + coeffecient: 1.5 + - platform: combination + type: max + name: Max of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: mean + name: Mean of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: median + name: Median of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: min + name: Min of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: most_recently_updated + name: Most recently updated of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: range + name: Range of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: sum + name: Sum of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 diff --git a/tests/components/combination/test.esp8266.yaml b/tests/components/combination/test.esp8266.yaml new file mode 100644 index 0000000000..62246190af --- /dev/null +++ b/tests/components/combination/test.esp8266.yaml @@ -0,0 +1,76 @@ +sensor: + - platform: template + id: template_temperature1 + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature2 + lambda: |- + if (millis() > 20000) { + return 0.8; + } else { + return 0.0; + } + - platform: combination + type: kalman + name: Kalman-filtered temperature + process_std_dev: 0.00139 + sources: + - source: template_temperature1 + error: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + error: 1.5 + - platform: combination + type: linear + name: Linearly combined temperatures + sources: + - source: template_temperature1 + coeffecient: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + coeffecient: 1.5 + - platform: combination + type: max + name: Max of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: mean + name: Mean of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: median + name: Median of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: min + name: Min of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: most_recently_updated + name: Most recently updated of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: range + name: Range of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: sum + name: Sum of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 diff --git a/tests/components/combination/test.rp2040.yaml b/tests/components/combination/test.rp2040.yaml new file mode 100644 index 0000000000..62246190af --- /dev/null +++ b/tests/components/combination/test.rp2040.yaml @@ -0,0 +1,76 @@ +sensor: + - platform: template + id: template_temperature1 + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature2 + lambda: |- + if (millis() > 20000) { + return 0.8; + } else { + return 0.0; + } + - platform: combination + type: kalman + name: Kalman-filtered temperature + process_std_dev: 0.00139 + sources: + - source: template_temperature1 + error: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + error: 1.5 + - platform: combination + type: linear + name: Linearly combined temperatures + sources: + - source: template_temperature1 + coeffecient: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + coeffecient: 1.5 + - platform: combination + type: max + name: Max of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: mean + name: Mean of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: median + name: Median of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: min + name: Min of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: most_recently_updated + name: Most recently updated of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: range + name: Range of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: sum + name: Sum of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 diff --git a/tests/components/coolix/test.esp32-c3-idf.yaml b/tests/components/coolix/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..0f9518d2cf --- /dev/null +++ b/tests/components/coolix/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: coolix + name: Coolix Climate diff --git a/tests/components/coolix/test.esp32-c3.yaml b/tests/components/coolix/test.esp32-c3.yaml new file mode 100644 index 0000000000..0f9518d2cf --- /dev/null +++ b/tests/components/coolix/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: coolix + name: Coolix Climate diff --git a/tests/components/coolix/test.esp32-idf.yaml b/tests/components/coolix/test.esp32-idf.yaml new file mode 100644 index 0000000000..0f9518d2cf --- /dev/null +++ b/tests/components/coolix/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: coolix + name: Coolix Climate diff --git a/tests/components/coolix/test.esp32.yaml b/tests/components/coolix/test.esp32.yaml new file mode 100644 index 0000000000..0f9518d2cf --- /dev/null +++ b/tests/components/coolix/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: coolix + name: Coolix Climate diff --git a/tests/components/coolix/test.esp8266.yaml b/tests/components/coolix/test.esp8266.yaml new file mode 100644 index 0000000000..61de8c7558 --- /dev/null +++ b/tests/components/coolix/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: coolix + name: Coolix Climate diff --git a/tests/components/copy/test.esp32-c3-idf.yaml b/tests/components/copy/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..554638f462 --- /dev/null +++ b/tests/components/copy/test.esp32-c3-idf.yaml @@ -0,0 +1,23 @@ +output: + - platform: ledc + id: fan_output_1 + pin: 2 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 + - platform: copy + source_id: fan_speed + name: Fan Speed Copy + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy diff --git a/tests/components/copy/test.esp32-c3.yaml b/tests/components/copy/test.esp32-c3.yaml new file mode 100644 index 0000000000..554638f462 --- /dev/null +++ b/tests/components/copy/test.esp32-c3.yaml @@ -0,0 +1,23 @@ +output: + - platform: ledc + id: fan_output_1 + pin: 2 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 + - platform: copy + source_id: fan_speed + name: Fan Speed Copy + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy diff --git a/tests/components/copy/test.esp32-idf.yaml b/tests/components/copy/test.esp32-idf.yaml new file mode 100644 index 0000000000..806dbfe9f3 --- /dev/null +++ b/tests/components/copy/test.esp32-idf.yaml @@ -0,0 +1,23 @@ +output: + - platform: ledc + id: fan_output_1 + pin: 12 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 + - platform: copy + source_id: fan_speed + name: Fan Speed Copy + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy diff --git a/tests/components/copy/test.esp32.yaml b/tests/components/copy/test.esp32.yaml new file mode 100644 index 0000000000..806dbfe9f3 --- /dev/null +++ b/tests/components/copy/test.esp32.yaml @@ -0,0 +1,23 @@ +output: + - platform: ledc + id: fan_output_1 + pin: 12 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 + - platform: copy + source_id: fan_speed + name: Fan Speed Copy + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy diff --git a/tests/components/copy/test.esp8266.yaml b/tests/components/copy/test.esp8266.yaml new file mode 100644 index 0000000000..1521e5f279 --- /dev/null +++ b/tests/components/copy/test.esp8266.yaml @@ -0,0 +1,23 @@ +output: + - platform: esp8266_pwm + id: fan_output_1 + pin: 12 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 + - platform: copy + source_id: fan_speed + name: Fan Speed Copy + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy diff --git a/tests/components/copy/test.rp2040.yaml b/tests/components/copy/test.rp2040.yaml new file mode 100644 index 0000000000..42e5eb8000 --- /dev/null +++ b/tests/components/copy/test.rp2040.yaml @@ -0,0 +1,23 @@ +output: + - platform: rp2040_pwm + id: fan_output_1 + pin: 12 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 + - platform: copy + source_id: fan_speed + name: Fan Speed Copy + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy diff --git a/tests/components/cs5460a/test.esp32-c3-idf.yaml b/tests/components/cs5460a/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4ce21783a3 --- /dev/null +++ b/tests/components/cs5460a/test.esp32-c3-idf.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_cs5460a + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: cs5460a + id: cs5460a1 + cs_pin: 8 + current: + name: Socket current + voltage: + name: Mains voltage + power: + name: Socket power + on_value: + then: + cs5460a.restart: cs5460a1 + samples: 1600 + pga_gain: 10X + current_gain: 0.01 + voltage_gain: 0.000573 + current_hpf: true + voltage_hpf: true + phase_offset: 20 + pulse_energy: 0.01 kWh diff --git a/tests/components/cs5460a/test.esp32-c3.yaml b/tests/components/cs5460a/test.esp32-c3.yaml new file mode 100644 index 0000000000..4ce21783a3 --- /dev/null +++ b/tests/components/cs5460a/test.esp32-c3.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_cs5460a + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: cs5460a + id: cs5460a1 + cs_pin: 8 + current: + name: Socket current + voltage: + name: Mains voltage + power: + name: Socket power + on_value: + then: + cs5460a.restart: cs5460a1 + samples: 1600 + pga_gain: 10X + current_gain: 0.01 + voltage_gain: 0.000573 + current_hpf: true + voltage_hpf: true + phase_offset: 20 + pulse_energy: 0.01 kWh diff --git a/tests/components/cs5460a/test.esp32-idf.yaml b/tests/components/cs5460a/test.esp32-idf.yaml new file mode 100644 index 0000000000..e7eb1cbd73 --- /dev/null +++ b/tests/components/cs5460a/test.esp32-idf.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_cs5460a + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: cs5460a + id: cs5460a1 + cs_pin: 12 + current: + name: Socket current + voltage: + name: Mains voltage + power: + name: Socket power + on_value: + then: + cs5460a.restart: cs5460a1 + samples: 1600 + pga_gain: 10X + current_gain: 0.01 + voltage_gain: 0.000573 + current_hpf: true + voltage_hpf: true + phase_offset: 20 + pulse_energy: 0.01 kWh diff --git a/tests/components/cs5460a/test.esp32.yaml b/tests/components/cs5460a/test.esp32.yaml new file mode 100644 index 0000000000..e7eb1cbd73 --- /dev/null +++ b/tests/components/cs5460a/test.esp32.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_cs5460a + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: cs5460a + id: cs5460a1 + cs_pin: 12 + current: + name: Socket current + voltage: + name: Mains voltage + power: + name: Socket power + on_value: + then: + cs5460a.restart: cs5460a1 + samples: 1600 + pga_gain: 10X + current_gain: 0.01 + voltage_gain: 0.000573 + current_hpf: true + voltage_hpf: true + phase_offset: 20 + pulse_energy: 0.01 kWh diff --git a/tests/components/cs5460a/test.esp8266.yaml b/tests/components/cs5460a/test.esp8266.yaml new file mode 100644 index 0000000000..c5a458d0ec --- /dev/null +++ b/tests/components/cs5460a/test.esp8266.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_cs5460a + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: cs5460a + id: cs5460a1 + cs_pin: 15 + current: + name: Socket current + voltage: + name: Mains voltage + power: + name: Socket power + on_value: + then: + cs5460a.restart: cs5460a1 + samples: 1600 + pga_gain: 10X + current_gain: 0.01 + voltage_gain: 0.000573 + current_hpf: true + voltage_hpf: true + phase_offset: 20 + pulse_energy: 0.01 kWh diff --git a/tests/components/cs5460a/test.rp2040.yaml b/tests/components/cs5460a/test.rp2040.yaml new file mode 100644 index 0000000000..f3daf7d72d --- /dev/null +++ b/tests/components/cs5460a/test.rp2040.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_cs5460a + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: cs5460a + id: cs5460a1 + cs_pin: 6 + current: + name: Socket current + voltage: + name: Mains voltage + power: + name: Socket power + on_value: + then: + cs5460a.restart: cs5460a1 + samples: 1600 + pga_gain: 10X + current_gain: 0.01 + voltage_gain: 0.000573 + current_hpf: true + voltage_hpf: true + phase_offset: 20 + pulse_energy: 0.01 kWh diff --git a/tests/components/cse7761/test.esp32-c3-idf.yaml b/tests/components/cse7761/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..581db24fd5 --- /dev/null +++ b/tests/components/cse7761/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_cse7761 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 38400 + +sensor: + - platform: cse7761 + voltage: + name: CSE7761 Voltage + current_1: + name: CSE7761 Current 1 + current_2: + name: CSE7761 Current 2 + active_power_1: + name: CSE7761 Active Power 1 + active_power_2: + name: CSE7761 Active Power 2 diff --git a/tests/components/cse7761/test.esp32-c3.yaml b/tests/components/cse7761/test.esp32-c3.yaml new file mode 100644 index 0000000000..581db24fd5 --- /dev/null +++ b/tests/components/cse7761/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_cse7761 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 38400 + +sensor: + - platform: cse7761 + voltage: + name: CSE7761 Voltage + current_1: + name: CSE7761 Current 1 + current_2: + name: CSE7761 Current 2 + active_power_1: + name: CSE7761 Active Power 1 + active_power_2: + name: CSE7761 Active Power 2 diff --git a/tests/components/cse7761/test.esp32-idf.yaml b/tests/components/cse7761/test.esp32-idf.yaml new file mode 100644 index 0000000000..4174e9a92e --- /dev/null +++ b/tests/components/cse7761/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_cse7761 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 38400 + +sensor: + - platform: cse7761 + voltage: + name: CSE7761 Voltage + current_1: + name: CSE7761 Current 1 + current_2: + name: CSE7761 Current 2 + active_power_1: + name: CSE7761 Active Power 1 + active_power_2: + name: CSE7761 Active Power 2 diff --git a/tests/components/cse7761/test.esp32.yaml b/tests/components/cse7761/test.esp32.yaml new file mode 100644 index 0000000000..4174e9a92e --- /dev/null +++ b/tests/components/cse7761/test.esp32.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_cse7761 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 38400 + +sensor: + - platform: cse7761 + voltage: + name: CSE7761 Voltage + current_1: + name: CSE7761 Current 1 + current_2: + name: CSE7761 Current 2 + active_power_1: + name: CSE7761 Active Power 1 + active_power_2: + name: CSE7761 Active Power 2 diff --git a/tests/components/cse7761/test.esp8266.yaml b/tests/components/cse7761/test.esp8266.yaml new file mode 100644 index 0000000000..581db24fd5 --- /dev/null +++ b/tests/components/cse7761/test.esp8266.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_cse7761 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 38400 + +sensor: + - platform: cse7761 + voltage: + name: CSE7761 Voltage + current_1: + name: CSE7761 Current 1 + current_2: + name: CSE7761 Current 2 + active_power_1: + name: CSE7761 Active Power 1 + active_power_2: + name: CSE7761 Active Power 2 diff --git a/tests/components/cse7761/test.rp2040.yaml b/tests/components/cse7761/test.rp2040.yaml new file mode 100644 index 0000000000..581db24fd5 --- /dev/null +++ b/tests/components/cse7761/test.rp2040.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_cse7761 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 38400 + +sensor: + - platform: cse7761 + voltage: + name: CSE7761 Voltage + current_1: + name: CSE7761 Current 1 + current_2: + name: CSE7761 Current 2 + active_power_1: + name: CSE7761 Active Power 1 + active_power_2: + name: CSE7761 Active Power 2 diff --git a/tests/components/cse7766/test.esp32-c3-idf.yaml b/tests/components/cse7766/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..432cc0a80e --- /dev/null +++ b/tests/components/cse7766/test.esp32-c3-idf.yaml @@ -0,0 +1,16 @@ +uart: + - id: uart_cse7766 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +sensor: + - platform: cse7766 + voltage: + name: CSE7766 Voltage + current: + name: CSE7766 Current + power: + name: CSE776 Power diff --git a/tests/components/cse7766/test.esp32-c3.yaml b/tests/components/cse7766/test.esp32-c3.yaml new file mode 100644 index 0000000000..432cc0a80e --- /dev/null +++ b/tests/components/cse7766/test.esp32-c3.yaml @@ -0,0 +1,16 @@ +uart: + - id: uart_cse7766 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +sensor: + - platform: cse7766 + voltage: + name: CSE7766 Voltage + current: + name: CSE7766 Current + power: + name: CSE776 Power diff --git a/tests/components/cse7766/test.esp32-idf.yaml b/tests/components/cse7766/test.esp32-idf.yaml new file mode 100644 index 0000000000..f94cd0f7d8 --- /dev/null +++ b/tests/components/cse7766/test.esp32-idf.yaml @@ -0,0 +1,16 @@ +uart: + - id: uart_cse7766 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 4800 + +sensor: + - platform: cse7766 + voltage: + name: CSE7766 Voltage + current: + name: CSE7766 Current + power: + name: CSE776 Power diff --git a/tests/components/cse7766/test.esp32.yaml b/tests/components/cse7766/test.esp32.yaml new file mode 100644 index 0000000000..f94cd0f7d8 --- /dev/null +++ b/tests/components/cse7766/test.esp32.yaml @@ -0,0 +1,16 @@ +uart: + - id: uart_cse7766 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 4800 + +sensor: + - platform: cse7766 + voltage: + name: CSE7766 Voltage + current: + name: CSE7766 Current + power: + name: CSE776 Power diff --git a/tests/components/cse7766/test.esp8266.yaml b/tests/components/cse7766/test.esp8266.yaml new file mode 100644 index 0000000000..432cc0a80e --- /dev/null +++ b/tests/components/cse7766/test.esp8266.yaml @@ -0,0 +1,16 @@ +uart: + - id: uart_cse7766 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +sensor: + - platform: cse7766 + voltage: + name: CSE7766 Voltage + current: + name: CSE7766 Current + power: + name: CSE776 Power diff --git a/tests/components/cse7766/test.rp2040.yaml b/tests/components/cse7766/test.rp2040.yaml new file mode 100644 index 0000000000..432cc0a80e --- /dev/null +++ b/tests/components/cse7766/test.rp2040.yaml @@ -0,0 +1,16 @@ +uart: + - id: uart_cse7766 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +sensor: + - platform: cse7766 + voltage: + name: CSE7766 Voltage + current: + name: CSE7766 Current + power: + name: CSE776 Power diff --git a/tests/components/cst226/test.esp32-c3.yaml b/tests/components/cst226/test.esp32-c3.yaml new file mode 100644 index 0000000000..4cbf38ef50 --- /dev/null +++ b/tests/components/cst226/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_id_1 + clk_pin: GPIO7 + mosi_pin: GPIO6 + interface: any + +display: + - platform: ili9xxx + id: displ8 + model: ili9342 + cs_pin: GPIO5 + dc_pin: GPIO4 + reset_pin: + number: GPIO21 + +i2c: + scl: GPIO18 + sda: GPIO8 + +touchscreen: + - platform: cst226 + interrupt_pin: GPIO3 + reset_pin: GPIO20 + diff --git a/tests/components/cst816/test.esp32.yaml b/tests/components/cst816/test.esp32.yaml new file mode 100644 index 0000000000..f8deea6e98 --- /dev/null +++ b/tests/components/cst816/test.esp32.yaml @@ -0,0 +1,36 @@ +touchscreen: + - platform: cst816 + id: my_touchscreen + interrupt_pin: + number: 21 + reset_pin: GPIO16 + transform: + mirror_x: false + mirror_y: false + swap_xy: false + +i2c: + sda: 3 + scl: 2 + +display: + - id: my_display + platform: ili9xxx + dimensions: 480x320 + model: ST7796 + cs_pin: 15 + dc_pin: 20 + reset_pin: 22 + transform: + swap_xy: true + mirror_x: true + mirror_y: true + auto_clear_enabled: false + +spi: + clk_pin: 14 + mosi_pin: 13 + +binary_sensor: + - platform: cst816 + name: Home Button diff --git a/tests/components/ct_clamp/test.esp32-c3-idf.yaml b/tests/components/ct_clamp/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..e25acc95e1 --- /dev/null +++ b/tests/components/ct_clamp/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: esp_adc_sensor + pin: 0 + - platform: ct_clamp + sensor: esp_adc_sensor + name: CT Clamp + sample_duration: 500ms + update_interval: 5s diff --git a/tests/components/ct_clamp/test.esp32-c3.yaml b/tests/components/ct_clamp/test.esp32-c3.yaml new file mode 100644 index 0000000000..e25acc95e1 --- /dev/null +++ b/tests/components/ct_clamp/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: esp_adc_sensor + pin: 0 + - platform: ct_clamp + sensor: esp_adc_sensor + name: CT Clamp + sample_duration: 500ms + update_interval: 5s diff --git a/tests/components/ct_clamp/test.esp32-idf.yaml b/tests/components/ct_clamp/test.esp32-idf.yaml new file mode 100644 index 0000000000..1ea964fa96 --- /dev/null +++ b/tests/components/ct_clamp/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: esp_adc_sensor + pin: 39 + - platform: ct_clamp + sensor: esp_adc_sensor + name: CT Clamp + sample_duration: 500ms + update_interval: 5s diff --git a/tests/components/ct_clamp/test.esp32.yaml b/tests/components/ct_clamp/test.esp32.yaml new file mode 100644 index 0000000000..1ea964fa96 --- /dev/null +++ b/tests/components/ct_clamp/test.esp32.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: esp_adc_sensor + pin: 39 + - platform: ct_clamp + sensor: esp_adc_sensor + name: CT Clamp + sample_duration: 500ms + update_interval: 5s diff --git a/tests/components/ct_clamp/test.esp8266.yaml b/tests/components/ct_clamp/test.esp8266.yaml new file mode 100644 index 0000000000..9c7126480d --- /dev/null +++ b/tests/components/ct_clamp/test.esp8266.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: esp_adc_sensor + pin: A0 + - platform: ct_clamp + sensor: esp_adc_sensor + name: CT Clamp + sample_duration: 500ms + update_interval: 5s diff --git a/tests/components/ct_clamp/test.rp2040.yaml b/tests/components/ct_clamp/test.rp2040.yaml new file mode 100644 index 0000000000..47077308aa --- /dev/null +++ b/tests/components/ct_clamp/test.rp2040.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: esp_adc_sensor + pin: 26 + - platform: ct_clamp + sensor: esp_adc_sensor + name: CT Clamp + sample_duration: 500ms + update_interval: 5s diff --git a/tests/components/current_based/test.esp32-c3-idf.yaml b/tests/components/current_based/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..69ab8830c3 --- /dev/null +++ b/tests/components/current_based/test.esp32-c3-idf.yaml @@ -0,0 +1,68 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: current_based + name: Current Based Cover + id: current_based_cover + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: template_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: template_switch2 + stop_action: + - switch.turn_off: template_switch1 + - switch.turn_off: template_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: Malfunction Detected diff --git a/tests/components/current_based/test.esp32-c3.yaml b/tests/components/current_based/test.esp32-c3.yaml new file mode 100644 index 0000000000..69ab8830c3 --- /dev/null +++ b/tests/components/current_based/test.esp32-c3.yaml @@ -0,0 +1,68 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: current_based + name: Current Based Cover + id: current_based_cover + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: template_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: template_switch2 + stop_action: + - switch.turn_off: template_switch1 + - switch.turn_off: template_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: Malfunction Detected diff --git a/tests/components/current_based/test.esp32-idf.yaml b/tests/components/current_based/test.esp32-idf.yaml new file mode 100644 index 0000000000..90781120bc --- /dev/null +++ b/tests/components/current_based/test.esp32-idf.yaml @@ -0,0 +1,68 @@ +i2c: + - id: i2c_ade7953 + scl: 16 + sda: 17 + +sensor: + - platform: ade7953_i2c + irq_pin: 15 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: current_based + name: Current Based Cover + id: current_based_cover + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: template_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: template_switch2 + stop_action: + - switch.turn_off: template_switch1 + - switch.turn_off: template_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: Malfunction Detected diff --git a/tests/components/current_based/test.esp32.yaml b/tests/components/current_based/test.esp32.yaml new file mode 100644 index 0000000000..90781120bc --- /dev/null +++ b/tests/components/current_based/test.esp32.yaml @@ -0,0 +1,68 @@ +i2c: + - id: i2c_ade7953 + scl: 16 + sda: 17 + +sensor: + - platform: ade7953_i2c + irq_pin: 15 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: current_based + name: Current Based Cover + id: current_based_cover + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: template_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: template_switch2 + stop_action: + - switch.turn_off: template_switch1 + - switch.turn_off: template_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: Malfunction Detected diff --git a/tests/components/current_based/test.esp8266.yaml b/tests/components/current_based/test.esp8266.yaml new file mode 100644 index 0000000000..42d6d4676b --- /dev/null +++ b/tests/components/current_based/test.esp8266.yaml @@ -0,0 +1,68 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 15 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: current_based + name: Current Based Cover + id: current_based_cover + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: template_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: template_switch2 + stop_action: + - switch.turn_off: template_switch1 + - switch.turn_off: template_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: Malfunction Detected diff --git a/tests/components/current_based/test.rp2040.yaml b/tests/components/current_based/test.rp2040.yaml new file mode 100644 index 0000000000..69ab8830c3 --- /dev/null +++ b/tests/components/current_based/test.rp2040.yaml @@ -0,0 +1,68 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: current_based + name: Current Based Cover + id: current_based_cover + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: template_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: template_switch2 + stop_action: + - switch.turn_off: template_switch1 + - switch.turn_off: template_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: Malfunction Detected diff --git a/tests/components/cwww/test.esp32-c3-idf.yaml b/tests/components/cwww/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..2760a167ee --- /dev/null +++ b/tests/components/cwww/test.esp32-c3-idf.yaml @@ -0,0 +1,19 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + channel: 0 + - platform: ledc + id: light_output_2 + pin: 2 + channel: 1 + phase_angle: 180° + +light: + - platform: cwww + name: CWWW Light + cold_white: light_output_1 + warm_white: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true diff --git a/tests/components/cwww/test.esp32-c3.yaml b/tests/components/cwww/test.esp32-c3.yaml new file mode 100644 index 0000000000..c829ca2a2b --- /dev/null +++ b/tests/components/cwww/test.esp32-c3.yaml @@ -0,0 +1,16 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + +light: + - platform: cwww + name: CWWW Light + cold_white: light_output_1 + warm_white: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true diff --git a/tests/components/cwww/test.esp32-idf.yaml b/tests/components/cwww/test.esp32-idf.yaml new file mode 100644 index 0000000000..27fa160e56 --- /dev/null +++ b/tests/components/cwww/test.esp32-idf.yaml @@ -0,0 +1,19 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + channel: 0 + - platform: ledc + id: light_output_2 + pin: 13 + channel: 1 + phase_angle: 180° + +light: + - platform: cwww + name: CWWW Light + cold_white: light_output_1 + warm_white: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true diff --git a/tests/components/cwww/test.esp32.yaml b/tests/components/cwww/test.esp32.yaml new file mode 100644 index 0000000000..f108d96ad3 --- /dev/null +++ b/tests/components/cwww/test.esp32.yaml @@ -0,0 +1,16 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + +light: + - platform: cwww + name: CWWW Light + cold_white: light_output_1 + warm_white: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true diff --git a/tests/components/cwww/test.esp8266.yaml b/tests/components/cwww/test.esp8266.yaml new file mode 100644 index 0000000000..50c311f616 --- /dev/null +++ b/tests/components/cwww/test.esp8266.yaml @@ -0,0 +1,16 @@ +output: + - platform: esp8266_pwm + id: light_output_1 + pin: 12 + - platform: esp8266_pwm + id: light_output_2 + pin: 13 + +light: + - platform: cwww + name: CWWW Light + cold_white: light_output_1 + warm_white: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true diff --git a/tests/components/cwww/test.rp2040.yaml b/tests/components/cwww/test.rp2040.yaml new file mode 100644 index 0000000000..505d67f862 --- /dev/null +++ b/tests/components/cwww/test.rp2040.yaml @@ -0,0 +1,16 @@ +output: + - platform: rp2040_pwm + id: light_output_1 + pin: 12 + - platform: rp2040_pwm + id: light_output_2 + pin: 13 + +light: + - platform: cwww + name: CWWW Light + cold_white: light_output_1 + warm_white: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true diff --git a/tests/components/dac7678/test.esp32-c3-idf.yaml b/tests/components/dac7678/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..7e3455bb68 --- /dev/null +++ b/tests/components/dac7678/test.esp32-c3-idf.yaml @@ -0,0 +1,43 @@ +i2c: + - id: i2c_dac7678 + scl: 5 + sda: 4 + +dac7678: + address: 0x4A + id: dac7678_hub + internal_reference: true + +output: + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 0 + id: dac7678_1_ch0 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 1 + id: dac7678_1_ch1 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 2 + id: dac7678_1_ch2 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 3 + id: dac7678_1_ch3 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 4 + id: dac7678_1_ch4 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 5 + id: dac7678_1_ch5 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 6 + id: dac7678_1_ch6 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 7 + id: dac7678_1_ch7 diff --git a/tests/components/dac7678/test.esp32-c3.yaml b/tests/components/dac7678/test.esp32-c3.yaml new file mode 100644 index 0000000000..7e3455bb68 --- /dev/null +++ b/tests/components/dac7678/test.esp32-c3.yaml @@ -0,0 +1,43 @@ +i2c: + - id: i2c_dac7678 + scl: 5 + sda: 4 + +dac7678: + address: 0x4A + id: dac7678_hub + internal_reference: true + +output: + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 0 + id: dac7678_1_ch0 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 1 + id: dac7678_1_ch1 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 2 + id: dac7678_1_ch2 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 3 + id: dac7678_1_ch3 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 4 + id: dac7678_1_ch4 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 5 + id: dac7678_1_ch5 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 6 + id: dac7678_1_ch6 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 7 + id: dac7678_1_ch7 diff --git a/tests/components/dac7678/test.esp32-idf.yaml b/tests/components/dac7678/test.esp32-idf.yaml new file mode 100644 index 0000000000..946a7ca58d --- /dev/null +++ b/tests/components/dac7678/test.esp32-idf.yaml @@ -0,0 +1,43 @@ +i2c: + - id: i2c_dac7678 + scl: 16 + sda: 17 + +dac7678: + address: 0x4A + id: dac7678_hub + internal_reference: true + +output: + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 0 + id: dac7678_1_ch0 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 1 + id: dac7678_1_ch1 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 2 + id: dac7678_1_ch2 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 3 + id: dac7678_1_ch3 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 4 + id: dac7678_1_ch4 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 5 + id: dac7678_1_ch5 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 6 + id: dac7678_1_ch6 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 7 + id: dac7678_1_ch7 diff --git a/tests/components/dac7678/test.esp32.yaml b/tests/components/dac7678/test.esp32.yaml new file mode 100644 index 0000000000..946a7ca58d --- /dev/null +++ b/tests/components/dac7678/test.esp32.yaml @@ -0,0 +1,43 @@ +i2c: + - id: i2c_dac7678 + scl: 16 + sda: 17 + +dac7678: + address: 0x4A + id: dac7678_hub + internal_reference: true + +output: + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 0 + id: dac7678_1_ch0 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 1 + id: dac7678_1_ch1 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 2 + id: dac7678_1_ch2 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 3 + id: dac7678_1_ch3 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 4 + id: dac7678_1_ch4 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 5 + id: dac7678_1_ch5 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 6 + id: dac7678_1_ch6 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 7 + id: dac7678_1_ch7 diff --git a/tests/components/dac7678/test.esp8266.yaml b/tests/components/dac7678/test.esp8266.yaml new file mode 100644 index 0000000000..7e3455bb68 --- /dev/null +++ b/tests/components/dac7678/test.esp8266.yaml @@ -0,0 +1,43 @@ +i2c: + - id: i2c_dac7678 + scl: 5 + sda: 4 + +dac7678: + address: 0x4A + id: dac7678_hub + internal_reference: true + +output: + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 0 + id: dac7678_1_ch0 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 1 + id: dac7678_1_ch1 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 2 + id: dac7678_1_ch2 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 3 + id: dac7678_1_ch3 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 4 + id: dac7678_1_ch4 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 5 + id: dac7678_1_ch5 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 6 + id: dac7678_1_ch6 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 7 + id: dac7678_1_ch7 diff --git a/tests/components/dac7678/test.rp2040.yaml b/tests/components/dac7678/test.rp2040.yaml new file mode 100644 index 0000000000..7e3455bb68 --- /dev/null +++ b/tests/components/dac7678/test.rp2040.yaml @@ -0,0 +1,43 @@ +i2c: + - id: i2c_dac7678 + scl: 5 + sda: 4 + +dac7678: + address: 0x4A + id: dac7678_hub + internal_reference: true + +output: + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 0 + id: dac7678_1_ch0 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 1 + id: dac7678_1_ch1 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 2 + id: dac7678_1_ch2 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 3 + id: dac7678_1_ch3 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 4 + id: dac7678_1_ch4 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 5 + id: dac7678_1_ch5 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 6 + id: dac7678_1_ch6 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 7 + id: dac7678_1_ch7 diff --git a/tests/components/daikin/test.esp32.yaml b/tests/components/daikin/test.esp32.yaml new file mode 100644 index 0000000000..6672fe3e45 --- /dev/null +++ b/tests/components/daikin/test.esp32.yaml @@ -0,0 +1,12 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: heatpumpir + protocol: daikin + horizontal_default: middle + vertical_default: middle + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 diff --git a/tests/components/daikin/test.esp8266.yaml b/tests/components/daikin/test.esp8266.yaml new file mode 100644 index 0000000000..47f02bbad9 --- /dev/null +++ b/tests/components/daikin/test.esp8266.yaml @@ -0,0 +1,12 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: heatpumpir + protocol: daikin + horizontal_default: middle + vertical_default: middle + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 diff --git a/tests/components/daikin_arc/test.esp32.yaml b/tests/components/daikin_arc/test.esp32.yaml new file mode 100644 index 0000000000..a8556e8576 --- /dev/null +++ b/tests/components/daikin_arc/test.esp32.yaml @@ -0,0 +1,19 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + id: tsvr + +remote_receiver: + id: rcvr + pin: + number: 27 + inverted: true + mode: + input: true + pullup: true + tolerance: 40% + +climate: + - platform: daikin_arc + name: "AC" + receiver_id: rcvr diff --git a/tests/components/daikin_arc/test.esp8266.yaml b/tests/components/daikin_arc/test.esp8266.yaml new file mode 100644 index 0000000000..abf1b34a6e --- /dev/null +++ b/tests/components/daikin_arc/test.esp8266.yaml @@ -0,0 +1,19 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + id: tsvr + +remote_receiver: + id: rcvr + pin: + number: 2 + inverted: true + mode: + input: true + pullup: true + tolerance: 40% + +climate: + - platform: daikin_arc + name: "AC" + receiver_id: rcvr diff --git a/tests/components/daikin_brc/test.esp32-c3-idf.yaml b/tests/components/daikin_brc/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..89a5b00f5d --- /dev/null +++ b/tests/components/daikin_brc/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: daikin_brc + name: Daikin_brc Climate diff --git a/tests/components/daikin_brc/test.esp32-c3.yaml b/tests/components/daikin_brc/test.esp32-c3.yaml new file mode 100644 index 0000000000..89a5b00f5d --- /dev/null +++ b/tests/components/daikin_brc/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: daikin_brc + name: Daikin_brc Climate diff --git a/tests/components/daikin_brc/test.esp32-idf.yaml b/tests/components/daikin_brc/test.esp32-idf.yaml new file mode 100644 index 0000000000..89a5b00f5d --- /dev/null +++ b/tests/components/daikin_brc/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: daikin_brc + name: Daikin_brc Climate diff --git a/tests/components/daikin_brc/test.esp32.yaml b/tests/components/daikin_brc/test.esp32.yaml new file mode 100644 index 0000000000..89a5b00f5d --- /dev/null +++ b/tests/components/daikin_brc/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: daikin_brc + name: Daikin_brc Climate diff --git a/tests/components/daikin_brc/test.esp8266.yaml b/tests/components/daikin_brc/test.esp8266.yaml new file mode 100644 index 0000000000..b8c74803a2 --- /dev/null +++ b/tests/components/daikin_brc/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: daikin_brc + name: Daikin_brc Climate diff --git a/tests/components/dallas/test.esp32-c3-idf.yaml b/tests/components/dallas/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..7975977107 --- /dev/null +++ b/tests/components/dallas/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +dallas: + pin: 4 + +sensor: + - platform: dallas + address: 0x1C0000031EDD2A28 + name: Dallas Temperature + resolution: 9 + - platform: dallas + index: 1 + name: Dallas Temperature diff --git a/tests/components/dallas/test.esp32-c3.yaml b/tests/components/dallas/test.esp32-c3.yaml new file mode 100644 index 0000000000..7975977107 --- /dev/null +++ b/tests/components/dallas/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +dallas: + pin: 4 + +sensor: + - platform: dallas + address: 0x1C0000031EDD2A28 + name: Dallas Temperature + resolution: 9 + - platform: dallas + index: 1 + name: Dallas Temperature diff --git a/tests/components/dallas/test.esp32-idf.yaml b/tests/components/dallas/test.esp32-idf.yaml new file mode 100644 index 0000000000..7975977107 --- /dev/null +++ b/tests/components/dallas/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +dallas: + pin: 4 + +sensor: + - platform: dallas + address: 0x1C0000031EDD2A28 + name: Dallas Temperature + resolution: 9 + - platform: dallas + index: 1 + name: Dallas Temperature diff --git a/tests/components/dallas/test.esp32.yaml b/tests/components/dallas/test.esp32.yaml new file mode 100644 index 0000000000..7975977107 --- /dev/null +++ b/tests/components/dallas/test.esp32.yaml @@ -0,0 +1,11 @@ +dallas: + pin: 4 + +sensor: + - platform: dallas + address: 0x1C0000031EDD2A28 + name: Dallas Temperature + resolution: 9 + - platform: dallas + index: 1 + name: Dallas Temperature diff --git a/tests/components/dallas/test.esp8266.yaml b/tests/components/dallas/test.esp8266.yaml new file mode 100644 index 0000000000..7975977107 --- /dev/null +++ b/tests/components/dallas/test.esp8266.yaml @@ -0,0 +1,11 @@ +dallas: + pin: 4 + +sensor: + - platform: dallas + address: 0x1C0000031EDD2A28 + name: Dallas Temperature + resolution: 9 + - platform: dallas + index: 1 + name: Dallas Temperature diff --git a/tests/components/dallas/test.rp2040.yaml b/tests/components/dallas/test.rp2040.yaml new file mode 100644 index 0000000000..7975977107 --- /dev/null +++ b/tests/components/dallas/test.rp2040.yaml @@ -0,0 +1,11 @@ +dallas: + pin: 4 + +sensor: + - platform: dallas + address: 0x1C0000031EDD2A28 + name: Dallas Temperature + resolution: 9 + - platform: dallas + index: 1 + name: Dallas Temperature diff --git a/tests/components/daly_bms/test.esp32-c3-idf.yaml b/tests/components/daly_bms/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..237a6570b5 --- /dev/null +++ b/tests/components/daly_bms/test.esp32-c3-idf.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_daly_bms + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +daly_bms: + update_interval: 20s + +binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: Charging MOS + discharging_mos_enabled: + name: Discharging MOS + +sensor: + - platform: daly_bms + voltage: + name: Battery Voltage + current: + name: Battery Current + battery_level: + name: Battery Level + max_cell_voltage: + name: Max Cell Voltage + max_cell_voltage_number: + name: Max Cell Voltage Number + min_cell_voltage: + name: Min Cell Voltage + min_cell_voltage_number: + name: Min Cell Voltage Number + max_temperature: + name: Max Temperature + max_temperature_probe_number: + name: Max Temperature Probe Number + min_temperature: + name: Min Temperature + min_temperature_probe_number: + name: Min Temperature Probe Number + remaining_capacity: + name: Remaining Capacity + cells_number: + name: Cells Number + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + +text_sensor: + - platform: daly_bms + status: + name: BMS Status diff --git a/tests/components/daly_bms/test.esp32-c3.yaml b/tests/components/daly_bms/test.esp32-c3.yaml new file mode 100644 index 0000000000..237a6570b5 --- /dev/null +++ b/tests/components/daly_bms/test.esp32-c3.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_daly_bms + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +daly_bms: + update_interval: 20s + +binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: Charging MOS + discharging_mos_enabled: + name: Discharging MOS + +sensor: + - platform: daly_bms + voltage: + name: Battery Voltage + current: + name: Battery Current + battery_level: + name: Battery Level + max_cell_voltage: + name: Max Cell Voltage + max_cell_voltage_number: + name: Max Cell Voltage Number + min_cell_voltage: + name: Min Cell Voltage + min_cell_voltage_number: + name: Min Cell Voltage Number + max_temperature: + name: Max Temperature + max_temperature_probe_number: + name: Max Temperature Probe Number + min_temperature: + name: Min Temperature + min_temperature_probe_number: + name: Min Temperature Probe Number + remaining_capacity: + name: Remaining Capacity + cells_number: + name: Cells Number + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + +text_sensor: + - platform: daly_bms + status: + name: BMS Status diff --git a/tests/components/daly_bms/test.esp32-idf.yaml b/tests/components/daly_bms/test.esp32-idf.yaml new file mode 100644 index 0000000000..ec9607334d --- /dev/null +++ b/tests/components/daly_bms/test.esp32-idf.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_daly_bms + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 4800 + +daly_bms: + update_interval: 20s + +binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: Charging MOS + discharging_mos_enabled: + name: Discharging MOS + +sensor: + - platform: daly_bms + voltage: + name: Battery Voltage + current: + name: Battery Current + battery_level: + name: Battery Level + max_cell_voltage: + name: Max Cell Voltage + max_cell_voltage_number: + name: Max Cell Voltage Number + min_cell_voltage: + name: Min Cell Voltage + min_cell_voltage_number: + name: Min Cell Voltage Number + max_temperature: + name: Max Temperature + max_temperature_probe_number: + name: Max Temperature Probe Number + min_temperature: + name: Min Temperature + min_temperature_probe_number: + name: Min Temperature Probe Number + remaining_capacity: + name: Remaining Capacity + cells_number: + name: Cells Number + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + +text_sensor: + - platform: daly_bms + status: + name: BMS Status diff --git a/tests/components/daly_bms/test.esp32.yaml b/tests/components/daly_bms/test.esp32.yaml new file mode 100644 index 0000000000..ec9607334d --- /dev/null +++ b/tests/components/daly_bms/test.esp32.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_daly_bms + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 4800 + +daly_bms: + update_interval: 20s + +binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: Charging MOS + discharging_mos_enabled: + name: Discharging MOS + +sensor: + - platform: daly_bms + voltage: + name: Battery Voltage + current: + name: Battery Current + battery_level: + name: Battery Level + max_cell_voltage: + name: Max Cell Voltage + max_cell_voltage_number: + name: Max Cell Voltage Number + min_cell_voltage: + name: Min Cell Voltage + min_cell_voltage_number: + name: Min Cell Voltage Number + max_temperature: + name: Max Temperature + max_temperature_probe_number: + name: Max Temperature Probe Number + min_temperature: + name: Min Temperature + min_temperature_probe_number: + name: Min Temperature Probe Number + remaining_capacity: + name: Remaining Capacity + cells_number: + name: Cells Number + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + +text_sensor: + - platform: daly_bms + status: + name: BMS Status diff --git a/tests/components/daly_bms/test.esp8266.yaml b/tests/components/daly_bms/test.esp8266.yaml new file mode 100644 index 0000000000..237a6570b5 --- /dev/null +++ b/tests/components/daly_bms/test.esp8266.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_daly_bms + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +daly_bms: + update_interval: 20s + +binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: Charging MOS + discharging_mos_enabled: + name: Discharging MOS + +sensor: + - platform: daly_bms + voltage: + name: Battery Voltage + current: + name: Battery Current + battery_level: + name: Battery Level + max_cell_voltage: + name: Max Cell Voltage + max_cell_voltage_number: + name: Max Cell Voltage Number + min_cell_voltage: + name: Min Cell Voltage + min_cell_voltage_number: + name: Min Cell Voltage Number + max_temperature: + name: Max Temperature + max_temperature_probe_number: + name: Max Temperature Probe Number + min_temperature: + name: Min Temperature + min_temperature_probe_number: + name: Min Temperature Probe Number + remaining_capacity: + name: Remaining Capacity + cells_number: + name: Cells Number + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + +text_sensor: + - platform: daly_bms + status: + name: BMS Status diff --git a/tests/components/daly_bms/test.rp2040.yaml b/tests/components/daly_bms/test.rp2040.yaml new file mode 100644 index 0000000000..237a6570b5 --- /dev/null +++ b/tests/components/daly_bms/test.rp2040.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_daly_bms + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +daly_bms: + update_interval: 20s + +binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: Charging MOS + discharging_mos_enabled: + name: Discharging MOS + +sensor: + - platform: daly_bms + voltage: + name: Battery Voltage + current: + name: Battery Current + battery_level: + name: Battery Level + max_cell_voltage: + name: Max Cell Voltage + max_cell_voltage_number: + name: Max Cell Voltage Number + min_cell_voltage: + name: Min Cell Voltage + min_cell_voltage_number: + name: Min Cell Voltage Number + max_temperature: + name: Max Temperature + max_temperature_probe_number: + name: Max Temperature Probe Number + min_temperature: + name: Min Temperature + min_temperature_probe_number: + name: Min Temperature Probe Number + remaining_capacity: + name: Remaining Capacity + cells_number: + name: Cells Number + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + +text_sensor: + - platform: daly_bms + status: + name: BMS Status diff --git a/tests/components/datetime/date.all.yaml b/tests/components/datetime/date.all.yaml new file mode 100644 index 0000000000..3f5996bb8b --- /dev/null +++ b/tests/components/datetime/date.all.yaml @@ -0,0 +1 @@ +datetime: diff --git a/tests/components/debug/test.esp32-c3-idf.yaml b/tests/components/debug/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..5845beaa80 --- /dev/null +++ b/tests/components/debug/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +debug: diff --git a/tests/components/debug/test.esp32-c3.yaml b/tests/components/debug/test.esp32-c3.yaml new file mode 100644 index 0000000000..5845beaa80 --- /dev/null +++ b/tests/components/debug/test.esp32-c3.yaml @@ -0,0 +1 @@ +debug: diff --git a/tests/components/debug/test.esp32-idf.yaml b/tests/components/debug/test.esp32-idf.yaml new file mode 100644 index 0000000000..5845beaa80 --- /dev/null +++ b/tests/components/debug/test.esp32-idf.yaml @@ -0,0 +1 @@ +debug: diff --git a/tests/components/debug/test.esp32.yaml b/tests/components/debug/test.esp32.yaml new file mode 100644 index 0000000000..5845beaa80 --- /dev/null +++ b/tests/components/debug/test.esp32.yaml @@ -0,0 +1 @@ +debug: diff --git a/tests/components/debug/test.esp8266.yaml b/tests/components/debug/test.esp8266.yaml new file mode 100644 index 0000000000..5845beaa80 --- /dev/null +++ b/tests/components/debug/test.esp8266.yaml @@ -0,0 +1 @@ +debug: diff --git a/tests/components/debug/test.host.yaml b/tests/components/debug/test.host.yaml new file mode 100644 index 0000000000..5845beaa80 --- /dev/null +++ b/tests/components/debug/test.host.yaml @@ -0,0 +1 @@ +debug: diff --git a/tests/components/debug/test.rp2040.yaml b/tests/components/debug/test.rp2040.yaml new file mode 100644 index 0000000000..5845beaa80 --- /dev/null +++ b/tests/components/debug/test.rp2040.yaml @@ -0,0 +1 @@ +debug: diff --git a/tests/components/deep_sleep/test.esp32-c3-idf.yaml b/tests/components/deep_sleep/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..94942fd5b0 --- /dev/null +++ b/tests/components/deep_sleep/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +esphome: + on_boot: + then: + - deep_sleep.prevent + - delay: 1s + - deep_sleep.allow + +deep_sleep: + run_duration: + default: 10s + gpio_wakeup_reason: 30s + sleep_duration: 50s + wakeup_pin: 4 + wakeup_pin_mode: INVERT_WAKEUP diff --git a/tests/components/deep_sleep/test.esp32-c3.yaml b/tests/components/deep_sleep/test.esp32-c3.yaml new file mode 100644 index 0000000000..94942fd5b0 --- /dev/null +++ b/tests/components/deep_sleep/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +esphome: + on_boot: + then: + - deep_sleep.prevent + - delay: 1s + - deep_sleep.allow + +deep_sleep: + run_duration: + default: 10s + gpio_wakeup_reason: 30s + sleep_duration: 50s + wakeup_pin: 4 + wakeup_pin_mode: INVERT_WAKEUP diff --git a/tests/components/deep_sleep/test.esp32-idf.yaml b/tests/components/deep_sleep/test.esp32-idf.yaml new file mode 100644 index 0000000000..94942fd5b0 --- /dev/null +++ b/tests/components/deep_sleep/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +esphome: + on_boot: + then: + - deep_sleep.prevent + - delay: 1s + - deep_sleep.allow + +deep_sleep: + run_duration: + default: 10s + gpio_wakeup_reason: 30s + sleep_duration: 50s + wakeup_pin: 4 + wakeup_pin_mode: INVERT_WAKEUP diff --git a/tests/components/deep_sleep/test.esp32.yaml b/tests/components/deep_sleep/test.esp32.yaml new file mode 100644 index 0000000000..94942fd5b0 --- /dev/null +++ b/tests/components/deep_sleep/test.esp32.yaml @@ -0,0 +1,14 @@ +esphome: + on_boot: + then: + - deep_sleep.prevent + - delay: 1s + - deep_sleep.allow + +deep_sleep: + run_duration: + default: 10s + gpio_wakeup_reason: 30s + sleep_duration: 50s + wakeup_pin: 4 + wakeup_pin_mode: INVERT_WAKEUP diff --git a/tests/components/deep_sleep/test.esp8266.yaml b/tests/components/deep_sleep/test.esp8266.yaml new file mode 100644 index 0000000000..0992fa9696 --- /dev/null +++ b/tests/components/deep_sleep/test.esp8266.yaml @@ -0,0 +1,10 @@ +esphome: + on_boot: + then: + - deep_sleep.prevent + - delay: 1s + - deep_sleep.allow + +deep_sleep: + run_duration: 10s + sleep_duration: 50s diff --git a/tests/components/delonghi/test.esp32-c3-idf.yaml b/tests/components/delonghi/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..cfe0f837f0 --- /dev/null +++ b/tests/components/delonghi/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: delonghi + name: Delonghi Climate diff --git a/tests/components/delonghi/test.esp32-c3.yaml b/tests/components/delonghi/test.esp32-c3.yaml new file mode 100644 index 0000000000..cfe0f837f0 --- /dev/null +++ b/tests/components/delonghi/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: delonghi + name: Delonghi Climate diff --git a/tests/components/delonghi/test.esp32-idf.yaml b/tests/components/delonghi/test.esp32-idf.yaml new file mode 100644 index 0000000000..cfe0f837f0 --- /dev/null +++ b/tests/components/delonghi/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: delonghi + name: Delonghi Climate diff --git a/tests/components/delonghi/test.esp32.yaml b/tests/components/delonghi/test.esp32.yaml new file mode 100644 index 0000000000..cfe0f837f0 --- /dev/null +++ b/tests/components/delonghi/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: delonghi + name: Delonghi Climate diff --git a/tests/components/delonghi/test.esp8266.yaml b/tests/components/delonghi/test.esp8266.yaml new file mode 100644 index 0000000000..adc478a6e6 --- /dev/null +++ b/tests/components/delonghi/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: delonghi + name: Delonghi Climate diff --git a/tests/components/dfplayer/test.esp32-c3-idf.yaml b/tests/components/dfplayer/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..94355915a7 --- /dev/null +++ b/tests/components/dfplayer/test.esp32-c3-idf.yaml @@ -0,0 +1,42 @@ +esphome: + on_boot: + then: + - dfplayer.play: 5 + - dfplayer.play: + file: 4 + loop: true + - dfplayer.play_folder: + folder: 1 + file: 3 + - dfplayer.play_folder: + folder: 1 + loop: true + - dfplayer.set_device: + device: TF_CARD + - dfplayer.set_volume: 5 + - dfplayer.set_eq: ROCK + - dfplayer.play_next + - dfplayer.play_previous + - dfplayer.reset + - dfplayer.start + - dfplayer.pause + - dfplayer.stop + - dfplayer.random + - dfplayer.volume_up + - dfplayer.volume_down + - dfplayer.sleep + +uart: + - id: uart_dfplayer + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfplayer: + on_finished_playback: + then: + if: + condition: + not: dfplayer.is_playing + then: + logger.log: Playback finished event diff --git a/tests/components/dfplayer/test.esp32-c3.yaml b/tests/components/dfplayer/test.esp32-c3.yaml new file mode 100644 index 0000000000..94355915a7 --- /dev/null +++ b/tests/components/dfplayer/test.esp32-c3.yaml @@ -0,0 +1,42 @@ +esphome: + on_boot: + then: + - dfplayer.play: 5 + - dfplayer.play: + file: 4 + loop: true + - dfplayer.play_folder: + folder: 1 + file: 3 + - dfplayer.play_folder: + folder: 1 + loop: true + - dfplayer.set_device: + device: TF_CARD + - dfplayer.set_volume: 5 + - dfplayer.set_eq: ROCK + - dfplayer.play_next + - dfplayer.play_previous + - dfplayer.reset + - dfplayer.start + - dfplayer.pause + - dfplayer.stop + - dfplayer.random + - dfplayer.volume_up + - dfplayer.volume_down + - dfplayer.sleep + +uart: + - id: uart_dfplayer + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfplayer: + on_finished_playback: + then: + if: + condition: + not: dfplayer.is_playing + then: + logger.log: Playback finished event diff --git a/tests/components/dfplayer/test.esp32-idf.yaml b/tests/components/dfplayer/test.esp32-idf.yaml new file mode 100644 index 0000000000..03b44b8ca9 --- /dev/null +++ b/tests/components/dfplayer/test.esp32-idf.yaml @@ -0,0 +1,42 @@ +esphome: + on_boot: + then: + - dfplayer.play: 5 + - dfplayer.play: + file: 4 + loop: true + - dfplayer.play_folder: + folder: 1 + file: 3 + - dfplayer.play_folder: + folder: 1 + loop: true + - dfplayer.set_device: + device: TF_CARD + - dfplayer.set_volume: 5 + - dfplayer.set_eq: ROCK + - dfplayer.play_next + - dfplayer.play_previous + - dfplayer.reset + - dfplayer.start + - dfplayer.pause + - dfplayer.stop + - dfplayer.random + - dfplayer.volume_up + - dfplayer.volume_down + - dfplayer.sleep + +uart: + - id: uart_dfplayer + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +dfplayer: + on_finished_playback: + then: + if: + condition: + not: dfplayer.is_playing + then: + logger.log: Playback finished event diff --git a/tests/components/dfplayer/test.esp32.yaml b/tests/components/dfplayer/test.esp32.yaml new file mode 100644 index 0000000000..03b44b8ca9 --- /dev/null +++ b/tests/components/dfplayer/test.esp32.yaml @@ -0,0 +1,42 @@ +esphome: + on_boot: + then: + - dfplayer.play: 5 + - dfplayer.play: + file: 4 + loop: true + - dfplayer.play_folder: + folder: 1 + file: 3 + - dfplayer.play_folder: + folder: 1 + loop: true + - dfplayer.set_device: + device: TF_CARD + - dfplayer.set_volume: 5 + - dfplayer.set_eq: ROCK + - dfplayer.play_next + - dfplayer.play_previous + - dfplayer.reset + - dfplayer.start + - dfplayer.pause + - dfplayer.stop + - dfplayer.random + - dfplayer.volume_up + - dfplayer.volume_down + - dfplayer.sleep + +uart: + - id: uart_dfplayer + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +dfplayer: + on_finished_playback: + then: + if: + condition: + not: dfplayer.is_playing + then: + logger.log: Playback finished event diff --git a/tests/components/dfplayer/test.esp8266.yaml b/tests/components/dfplayer/test.esp8266.yaml new file mode 100644 index 0000000000..94355915a7 --- /dev/null +++ b/tests/components/dfplayer/test.esp8266.yaml @@ -0,0 +1,42 @@ +esphome: + on_boot: + then: + - dfplayer.play: 5 + - dfplayer.play: + file: 4 + loop: true + - dfplayer.play_folder: + folder: 1 + file: 3 + - dfplayer.play_folder: + folder: 1 + loop: true + - dfplayer.set_device: + device: TF_CARD + - dfplayer.set_volume: 5 + - dfplayer.set_eq: ROCK + - dfplayer.play_next + - dfplayer.play_previous + - dfplayer.reset + - dfplayer.start + - dfplayer.pause + - dfplayer.stop + - dfplayer.random + - dfplayer.volume_up + - dfplayer.volume_down + - dfplayer.sleep + +uart: + - id: uart_dfplayer + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfplayer: + on_finished_playback: + then: + if: + condition: + not: dfplayer.is_playing + then: + logger.log: Playback finished event diff --git a/tests/components/dfplayer/test.rp2040.yaml b/tests/components/dfplayer/test.rp2040.yaml new file mode 100644 index 0000000000..94355915a7 --- /dev/null +++ b/tests/components/dfplayer/test.rp2040.yaml @@ -0,0 +1,42 @@ +esphome: + on_boot: + then: + - dfplayer.play: 5 + - dfplayer.play: + file: 4 + loop: true + - dfplayer.play_folder: + folder: 1 + file: 3 + - dfplayer.play_folder: + folder: 1 + loop: true + - dfplayer.set_device: + device: TF_CARD + - dfplayer.set_volume: 5 + - dfplayer.set_eq: ROCK + - dfplayer.play_next + - dfplayer.play_previous + - dfplayer.reset + - dfplayer.start + - dfplayer.pause + - dfplayer.stop + - dfplayer.random + - dfplayer.volume_up + - dfplayer.volume_down + - dfplayer.sleep + +uart: + - id: uart_dfplayer + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfplayer: + on_finished_playback: + then: + if: + condition: + not: dfplayer.is_playing + then: + logger.log: Playback finished event diff --git a/tests/components/dfrobot_sen0395/test.esp32-c3-idf.yaml b/tests/components/dfrobot_sen0395/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..71b17cecd5 --- /dev/null +++ b/tests/components/dfrobot_sen0395/test.esp32-c3-idf.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - dfrobot_sen0395.settings: + id: mmwave + factory_reset: true + detection_segments: + - [0cm, 5m] + - 600cm + - !lambda "return 7;" + output_latency: + delay_after_detect: 0s + delay_after_disappear: 0s + sensitivity: 6 + - dfrobot_sen0395.reset + +uart: + - id: uart_dfrobot_sen0395 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfrobot_sen0395: + - id: mmwave + +binary_sensor: + - platform: dfrobot_sen0395 + id: mmwave_detected diff --git a/tests/components/dfrobot_sen0395/test.esp32-c3.yaml b/tests/components/dfrobot_sen0395/test.esp32-c3.yaml new file mode 100644 index 0000000000..71b17cecd5 --- /dev/null +++ b/tests/components/dfrobot_sen0395/test.esp32-c3.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - dfrobot_sen0395.settings: + id: mmwave + factory_reset: true + detection_segments: + - [0cm, 5m] + - 600cm + - !lambda "return 7;" + output_latency: + delay_after_detect: 0s + delay_after_disappear: 0s + sensitivity: 6 + - dfrobot_sen0395.reset + +uart: + - id: uart_dfrobot_sen0395 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfrobot_sen0395: + - id: mmwave + +binary_sensor: + - platform: dfrobot_sen0395 + id: mmwave_detected diff --git a/tests/components/dfrobot_sen0395/test.esp32-idf.yaml b/tests/components/dfrobot_sen0395/test.esp32-idf.yaml new file mode 100644 index 0000000000..5c06fc6660 --- /dev/null +++ b/tests/components/dfrobot_sen0395/test.esp32-idf.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - dfrobot_sen0395.settings: + id: mmwave + factory_reset: true + detection_segments: + - [0cm, 5m] + - 600cm + - !lambda "return 7;" + output_latency: + delay_after_detect: 0s + delay_after_disappear: 0s + sensitivity: 6 + - dfrobot_sen0395.reset + +uart: + - id: uart_dfrobot_sen0395 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +dfrobot_sen0395: + - id: mmwave + +binary_sensor: + - platform: dfrobot_sen0395 + id: mmwave_detected diff --git a/tests/components/dfrobot_sen0395/test.esp32.yaml b/tests/components/dfrobot_sen0395/test.esp32.yaml new file mode 100644 index 0000000000..5c06fc6660 --- /dev/null +++ b/tests/components/dfrobot_sen0395/test.esp32.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - dfrobot_sen0395.settings: + id: mmwave + factory_reset: true + detection_segments: + - [0cm, 5m] + - 600cm + - !lambda "return 7;" + output_latency: + delay_after_detect: 0s + delay_after_disappear: 0s + sensitivity: 6 + - dfrobot_sen0395.reset + +uart: + - id: uart_dfrobot_sen0395 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +dfrobot_sen0395: + - id: mmwave + +binary_sensor: + - platform: dfrobot_sen0395 + id: mmwave_detected diff --git a/tests/components/dfrobot_sen0395/test.esp8266.yaml b/tests/components/dfrobot_sen0395/test.esp8266.yaml new file mode 100644 index 0000000000..71b17cecd5 --- /dev/null +++ b/tests/components/dfrobot_sen0395/test.esp8266.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - dfrobot_sen0395.settings: + id: mmwave + factory_reset: true + detection_segments: + - [0cm, 5m] + - 600cm + - !lambda "return 7;" + output_latency: + delay_after_detect: 0s + delay_after_disappear: 0s + sensitivity: 6 + - dfrobot_sen0395.reset + +uart: + - id: uart_dfrobot_sen0395 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfrobot_sen0395: + - id: mmwave + +binary_sensor: + - platform: dfrobot_sen0395 + id: mmwave_detected diff --git a/tests/components/dfrobot_sen0395/test.rp2040.yaml b/tests/components/dfrobot_sen0395/test.rp2040.yaml new file mode 100644 index 0000000000..71b17cecd5 --- /dev/null +++ b/tests/components/dfrobot_sen0395/test.rp2040.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - dfrobot_sen0395.settings: + id: mmwave + factory_reset: true + detection_segments: + - [0cm, 5m] + - 600cm + - !lambda "return 7;" + output_latency: + delay_after_detect: 0s + delay_after_disappear: 0s + sensitivity: 6 + - dfrobot_sen0395.reset + +uart: + - id: uart_dfrobot_sen0395 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfrobot_sen0395: + - id: mmwave + +binary_sensor: + - platform: dfrobot_sen0395 + id: mmwave_detected diff --git a/tests/components/dht/test.esp32-c3-idf.yaml b/tests/components/dht/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..f134a324ca --- /dev/null +++ b/tests/components/dht/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: dht + pin: 4 + model: AM2302 + update_interval: 15s + temperature: + id: dht_temperature + name: DHT Temperature + humidity: + id: dht_humidity + name: DHT Humidity diff --git a/tests/components/dht/test.esp32-c3.yaml b/tests/components/dht/test.esp32-c3.yaml new file mode 100644 index 0000000000..f134a324ca --- /dev/null +++ b/tests/components/dht/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: dht + pin: 4 + model: AM2302 + update_interval: 15s + temperature: + id: dht_temperature + name: DHT Temperature + humidity: + id: dht_humidity + name: DHT Humidity diff --git a/tests/components/dht/test.esp32-idf.yaml b/tests/components/dht/test.esp32-idf.yaml new file mode 100644 index 0000000000..f134a324ca --- /dev/null +++ b/tests/components/dht/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: dht + pin: 4 + model: AM2302 + update_interval: 15s + temperature: + id: dht_temperature + name: DHT Temperature + humidity: + id: dht_humidity + name: DHT Humidity diff --git a/tests/components/dht/test.esp32.yaml b/tests/components/dht/test.esp32.yaml new file mode 100644 index 0000000000..f134a324ca --- /dev/null +++ b/tests/components/dht/test.esp32.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: dht + pin: 4 + model: AM2302 + update_interval: 15s + temperature: + id: dht_temperature + name: DHT Temperature + humidity: + id: dht_humidity + name: DHT Humidity diff --git a/tests/components/dht/test.esp8266.yaml b/tests/components/dht/test.esp8266.yaml new file mode 100644 index 0000000000..f134a324ca --- /dev/null +++ b/tests/components/dht/test.esp8266.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: dht + pin: 4 + model: AM2302 + update_interval: 15s + temperature: + id: dht_temperature + name: DHT Temperature + humidity: + id: dht_humidity + name: DHT Humidity diff --git a/tests/components/dht/test.rp2040.yaml b/tests/components/dht/test.rp2040.yaml new file mode 100644 index 0000000000..f134a324ca --- /dev/null +++ b/tests/components/dht/test.rp2040.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: dht + pin: 4 + model: AM2302 + update_interval: 15s + temperature: + id: dht_temperature + name: DHT Temperature + humidity: + id: dht_humidity + name: DHT Humidity diff --git a/tests/components/dht12/test.esp32-c3-idf.yaml b/tests/components/dht12/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..c06c20fd9f --- /dev/null +++ b/tests/components/dht12/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dht12 + scl: 5 + sda: 4 + +sensor: + - platform: dht12 + temperature: + name: DHT12 Temperature + humidity: + name: DHT12 Humidity + update_interval: 15s diff --git a/tests/components/dht12/test.esp32-c3.yaml b/tests/components/dht12/test.esp32-c3.yaml new file mode 100644 index 0000000000..c06c20fd9f --- /dev/null +++ b/tests/components/dht12/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dht12 + scl: 5 + sda: 4 + +sensor: + - platform: dht12 + temperature: + name: DHT12 Temperature + humidity: + name: DHT12 Humidity + update_interval: 15s diff --git a/tests/components/dht12/test.esp32-idf.yaml b/tests/components/dht12/test.esp32-idf.yaml new file mode 100644 index 0000000000..02a00f5df7 --- /dev/null +++ b/tests/components/dht12/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dht12 + scl: 16 + sda: 17 + +sensor: + - platform: dht12 + temperature: + name: DHT12 Temperature + humidity: + name: DHT12 Humidity + update_interval: 15s diff --git a/tests/components/dht12/test.esp32.yaml b/tests/components/dht12/test.esp32.yaml new file mode 100644 index 0000000000..02a00f5df7 --- /dev/null +++ b/tests/components/dht12/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dht12 + scl: 16 + sda: 17 + +sensor: + - platform: dht12 + temperature: + name: DHT12 Temperature + humidity: + name: DHT12 Humidity + update_interval: 15s diff --git a/tests/components/dht12/test.esp8266.yaml b/tests/components/dht12/test.esp8266.yaml new file mode 100644 index 0000000000..c06c20fd9f --- /dev/null +++ b/tests/components/dht12/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dht12 + scl: 5 + sda: 4 + +sensor: + - platform: dht12 + temperature: + name: DHT12 Temperature + humidity: + name: DHT12 Humidity + update_interval: 15s diff --git a/tests/components/dht12/test.rp2040.yaml b/tests/components/dht12/test.rp2040.yaml new file mode 100644 index 0000000000..c06c20fd9f --- /dev/null +++ b/tests/components/dht12/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dht12 + scl: 5 + sda: 4 + +sensor: + - platform: dht12 + temperature: + name: DHT12 Temperature + humidity: + name: DHT12 Humidity + update_interval: 15s diff --git a/tests/components/display/test.esp32.yaml b/tests/components/display/test.esp32.yaml new file mode 100644 index 0000000000..a22aa76780 --- /dev/null +++ b/tests/components/display/test.esp32.yaml @@ -0,0 +1,35 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 12 + dc_pin: 13 + reset_pin: 21 + lambda: |- + // Draw an analog clock in the center of the screen + int centerX = it.get_width() / 2; + int centerY = it.get_height() / 2; + int radius = min(it.get_width(), it.get_height()) / 4; + + // Draw border + it.circle(centerX, centerY, radius); + + // Draw hour ticks + for(int h = 0; h < 12; h++) { + int hourAngle = (h * 30) - 90; + + it.line_at_angle(centerX, centerY, hourAngle, radius - 10, radius); + } + + // Draw minute ticks + for(int m = 0; m < 60; m++) { + int minuteAngle = (m * 6) - 90; + + it.line_at_angle(centerX, centerY, minuteAngle, radius - 5, radius); + } diff --git a/tests/components/dps310/test.esp32-c3-idf.yaml b/tests/components/dps310/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..0e15e9ccc5 --- /dev/null +++ b/tests/components/dps310/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dps310 + scl: 5 + sda: 4 + +sensor: + - platform: dps310 + temperature: + name: DPS310 Temperature + pressure: + name: DPS310 Pressure + address: 0x77 diff --git a/tests/components/dps310/test.esp32-c3.yaml b/tests/components/dps310/test.esp32-c3.yaml new file mode 100644 index 0000000000..0e15e9ccc5 --- /dev/null +++ b/tests/components/dps310/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dps310 + scl: 5 + sda: 4 + +sensor: + - platform: dps310 + temperature: + name: DPS310 Temperature + pressure: + name: DPS310 Pressure + address: 0x77 diff --git a/tests/components/dps310/test.esp32-idf.yaml b/tests/components/dps310/test.esp32-idf.yaml new file mode 100644 index 0000000000..417cab5c40 --- /dev/null +++ b/tests/components/dps310/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dps310 + scl: 16 + sda: 17 + +sensor: + - platform: dps310 + temperature: + name: DPS310 Temperature + pressure: + name: DPS310 Pressure + address: 0x77 diff --git a/tests/components/dps310/test.esp32.yaml b/tests/components/dps310/test.esp32.yaml new file mode 100644 index 0000000000..417cab5c40 --- /dev/null +++ b/tests/components/dps310/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dps310 + scl: 16 + sda: 17 + +sensor: + - platform: dps310 + temperature: + name: DPS310 Temperature + pressure: + name: DPS310 Pressure + address: 0x77 diff --git a/tests/components/dps310/test.esp8266.yaml b/tests/components/dps310/test.esp8266.yaml new file mode 100644 index 0000000000..0e15e9ccc5 --- /dev/null +++ b/tests/components/dps310/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dps310 + scl: 5 + sda: 4 + +sensor: + - platform: dps310 + temperature: + name: DPS310 Temperature + pressure: + name: DPS310 Pressure + address: 0x77 diff --git a/tests/components/dps310/test.rp2040.yaml b/tests/components/dps310/test.rp2040.yaml new file mode 100644 index 0000000000..0e15e9ccc5 --- /dev/null +++ b/tests/components/dps310/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dps310 + scl: 5 + sda: 4 + +sensor: + - platform: dps310 + temperature: + name: DPS310 Temperature + pressure: + name: DPS310 Pressure + address: 0x77 diff --git a/tests/components/ds1307/test.esp32-c3-idf.yaml b/tests/components/ds1307/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..c309b9c212 --- /dev/null +++ b/tests/components/ds1307/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_ds1307 + scl: 5 + sda: 4 + +time: + - platform: ds1307 + id: ds1307_time + update_interval: never diff --git a/tests/components/ds1307/test.esp32-c3.yaml b/tests/components/ds1307/test.esp32-c3.yaml new file mode 100644 index 0000000000..c309b9c212 --- /dev/null +++ b/tests/components/ds1307/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_ds1307 + scl: 5 + sda: 4 + +time: + - platform: ds1307 + id: ds1307_time + update_interval: never diff --git a/tests/components/ds1307/test.esp32-idf.yaml b/tests/components/ds1307/test.esp32-idf.yaml new file mode 100644 index 0000000000..017c7aac92 --- /dev/null +++ b/tests/components/ds1307/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_ds1307 + scl: 16 + sda: 17 + +time: + - platform: ds1307 + id: ds1307_time + update_interval: never diff --git a/tests/components/ds1307/test.esp32.yaml b/tests/components/ds1307/test.esp32.yaml new file mode 100644 index 0000000000..017c7aac92 --- /dev/null +++ b/tests/components/ds1307/test.esp32.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_ds1307 + scl: 16 + sda: 17 + +time: + - platform: ds1307 + id: ds1307_time + update_interval: never diff --git a/tests/components/ds1307/test.esp8266.yaml b/tests/components/ds1307/test.esp8266.yaml new file mode 100644 index 0000000000..c309b9c212 --- /dev/null +++ b/tests/components/ds1307/test.esp8266.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_ds1307 + scl: 5 + sda: 4 + +time: + - platform: ds1307 + id: ds1307_time + update_interval: never diff --git a/tests/components/ds1307/test.rp2040.yaml b/tests/components/ds1307/test.rp2040.yaml new file mode 100644 index 0000000000..c309b9c212 --- /dev/null +++ b/tests/components/ds1307/test.rp2040.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_ds1307 + scl: 5 + sda: 4 + +time: + - platform: ds1307 + id: ds1307_time + update_interval: never diff --git a/tests/components/dsmr/test.esp32-c3.yaml b/tests/components/dsmr/test.esp32-c3.yaml new file mode 100644 index 0000000000..e813556be8 --- /dev/null +++ b/tests/components/dsmr/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_dsmr + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dsmr: + decryption_key: 00112233445566778899aabbccddeeff + max_telegram_length: 1000 + request_pin: 6 + request_interval: 20s + receive_timeout: 100ms diff --git a/tests/components/dsmr/test.esp32.yaml b/tests/components/dsmr/test.esp32.yaml new file mode 100644 index 0000000000..1fd0448ab3 --- /dev/null +++ b/tests/components/dsmr/test.esp32.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_dsmr + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +dsmr: + decryption_key: 00112233445566778899aabbccddeeff + max_telegram_length: 1000 + request_pin: 15 + request_interval: 20s + receive_timeout: 100ms diff --git a/tests/components/dsmr/test.esp8266.yaml b/tests/components/dsmr/test.esp8266.yaml new file mode 100644 index 0000000000..8037fb4b1a --- /dev/null +++ b/tests/components/dsmr/test.esp8266.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_dsmr + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dsmr: + decryption_key: 00112233445566778899aabbccddeeff + max_telegram_length: 1000 + request_pin: 15 + request_interval: 20s + receive_timeout: 100ms diff --git a/tests/components/dsmr/test.rp2040.yaml b/tests/components/dsmr/test.rp2040.yaml new file mode 100644 index 0000000000..e813556be8 --- /dev/null +++ b/tests/components/dsmr/test.rp2040.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_dsmr + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dsmr: + decryption_key: 00112233445566778899aabbccddeeff + max_telegram_length: 1000 + request_pin: 6 + request_interval: 20s + receive_timeout: 100ms diff --git a/tests/components/duty_cycle/test.esp32-c3-idf.yaml b/tests/components/duty_cycle/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..2b7f31efbd --- /dev/null +++ b/tests/components/duty_cycle/test.esp32-c3-idf.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: duty_cycle + pin: 4 + name: Duty Cycle Sensor diff --git a/tests/components/duty_cycle/test.esp32-c3.yaml b/tests/components/duty_cycle/test.esp32-c3.yaml new file mode 100644 index 0000000000..2b7f31efbd --- /dev/null +++ b/tests/components/duty_cycle/test.esp32-c3.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: duty_cycle + pin: 4 + name: Duty Cycle Sensor diff --git a/tests/components/duty_cycle/test.esp32-idf.yaml b/tests/components/duty_cycle/test.esp32-idf.yaml new file mode 100644 index 0000000000..2b7f31efbd --- /dev/null +++ b/tests/components/duty_cycle/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: duty_cycle + pin: 4 + name: Duty Cycle Sensor diff --git a/tests/components/duty_cycle/test.esp32.yaml b/tests/components/duty_cycle/test.esp32.yaml new file mode 100644 index 0000000000..2b7f31efbd --- /dev/null +++ b/tests/components/duty_cycle/test.esp32.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: duty_cycle + pin: 4 + name: Duty Cycle Sensor diff --git a/tests/components/duty_cycle/test.esp8266.yaml b/tests/components/duty_cycle/test.esp8266.yaml new file mode 100644 index 0000000000..2b7f31efbd --- /dev/null +++ b/tests/components/duty_cycle/test.esp8266.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: duty_cycle + pin: 4 + name: Duty Cycle Sensor diff --git a/tests/components/duty_cycle/test.rp2040.yaml b/tests/components/duty_cycle/test.rp2040.yaml new file mode 100644 index 0000000000..2b7f31efbd --- /dev/null +++ b/tests/components/duty_cycle/test.rp2040.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: duty_cycle + pin: 4 + name: Duty Cycle Sensor diff --git a/tests/components/duty_time/test.esp32-c3-idf.yaml b/tests/components/duty_time/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..28fa4afd1c --- /dev/null +++ b/tests/components/duty_time/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +sensor: + - platform: duty_time + name: Duty Time + sensor: bin1 diff --git a/tests/components/duty_time/test.esp32-c3.yaml b/tests/components/duty_time/test.esp32-c3.yaml new file mode 100644 index 0000000000..28fa4afd1c --- /dev/null +++ b/tests/components/duty_time/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +sensor: + - platform: duty_time + name: Duty Time + sensor: bin1 diff --git a/tests/components/duty_time/test.esp32-idf.yaml b/tests/components/duty_time/test.esp32-idf.yaml new file mode 100644 index 0000000000..28fa4afd1c --- /dev/null +++ b/tests/components/duty_time/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +sensor: + - platform: duty_time + name: Duty Time + sensor: bin1 diff --git a/tests/components/duty_time/test.esp32.yaml b/tests/components/duty_time/test.esp32.yaml new file mode 100644 index 0000000000..28fa4afd1c --- /dev/null +++ b/tests/components/duty_time/test.esp32.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +sensor: + - platform: duty_time + name: Duty Time + sensor: bin1 diff --git a/tests/components/duty_time/test.esp8266.yaml b/tests/components/duty_time/test.esp8266.yaml new file mode 100644 index 0000000000..28fa4afd1c --- /dev/null +++ b/tests/components/duty_time/test.esp8266.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +sensor: + - platform: duty_time + name: Duty Time + sensor: bin1 diff --git a/tests/components/duty_time/test.rp2040.yaml b/tests/components/duty_time/test.rp2040.yaml new file mode 100644 index 0000000000..28fa4afd1c --- /dev/null +++ b/tests/components/duty_time/test.rp2040.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +sensor: + - platform: duty_time + name: Duty Time + sensor: bin1 diff --git a/tests/components/e131/test.esp32-c3-idf.yaml b/tests/components/e131/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..25304cd3b4 --- /dev/null +++ b/tests/components/e131/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +e131: + +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + effects: + - e131: + universe: 1 diff --git a/tests/components/e131/test.esp32-c3.yaml b/tests/components/e131/test.esp32-c3.yaml new file mode 100644 index 0000000000..25304cd3b4 --- /dev/null +++ b/tests/components/e131/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +e131: + +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + effects: + - e131: + universe: 1 diff --git a/tests/components/e131/test.esp32-idf.yaml b/tests/components/e131/test.esp32-idf.yaml new file mode 100644 index 0000000000..25304cd3b4 --- /dev/null +++ b/tests/components/e131/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +e131: + +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + effects: + - e131: + universe: 1 diff --git a/tests/components/e131/test.esp32.yaml b/tests/components/e131/test.esp32.yaml new file mode 100644 index 0000000000..25304cd3b4 --- /dev/null +++ b/tests/components/e131/test.esp32.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +e131: + +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + effects: + - e131: + universe: 1 diff --git a/tests/components/e131/test.esp8266.yaml b/tests/components/e131/test.esp8266.yaml new file mode 100644 index 0000000000..54245014a5 --- /dev/null +++ b/tests/components/e131/test.esp8266.yaml @@ -0,0 +1,17 @@ +wifi: + ssid: MySSID + password: password1 + +e131: + +light: + - platform: neopixelbus + name: Neopixelbus Light + pin: 1 + type: GRBW + variant: SK6812 + method: ESP8266_UART0 + num_leds: 256 + effects: + - e131: + universe: 1 diff --git a/tests/components/e131/test.rp2040.yaml b/tests/components/e131/test.rp2040.yaml new file mode 100644 index 0000000000..0ae31f5403 --- /dev/null +++ b/tests/components/e131/test.rp2040.yaml @@ -0,0 +1,17 @@ +wifi: + ssid: MySSID + password: password1 + +e131: + +light: + - platform: rp2040_pio_led_strip + id: led_strip + pin: 2 + pio: 0 + num_leds: 256 + rgb_order: GRB + chipset: WS2812 + effects: + - e131: + universe: 1 diff --git a/tests/components/ee895/test.esp32-c3-idf.yaml b/tests/components/ee895/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4a36117fab --- /dev/null +++ b/tests/components/ee895/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_ee895 + scl: 5 + sda: 4 + +sensor: + - platform: ee895 + address: 0x5F + co2: + name: EE895 CO2 + temperature: + name: EE895 Temperature + pressure: + name: EE895 Pressure diff --git a/tests/components/ee895/test.esp32-c3.yaml b/tests/components/ee895/test.esp32-c3.yaml new file mode 100644 index 0000000000..4a36117fab --- /dev/null +++ b/tests/components/ee895/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_ee895 + scl: 5 + sda: 4 + +sensor: + - platform: ee895 + address: 0x5F + co2: + name: EE895 CO2 + temperature: + name: EE895 Temperature + pressure: + name: EE895 Pressure diff --git a/tests/components/ee895/test.esp32-idf.yaml b/tests/components/ee895/test.esp32-idf.yaml new file mode 100644 index 0000000000..241bdb9574 --- /dev/null +++ b/tests/components/ee895/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_ee895 + scl: 16 + sda: 17 + +sensor: + - platform: ee895 + address: 0x5F + co2: + name: EE895 CO2 + temperature: + name: EE895 Temperature + pressure: + name: EE895 Pressure diff --git a/tests/components/ee895/test.esp32.yaml b/tests/components/ee895/test.esp32.yaml new file mode 100644 index 0000000000..241bdb9574 --- /dev/null +++ b/tests/components/ee895/test.esp32.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_ee895 + scl: 16 + sda: 17 + +sensor: + - platform: ee895 + address: 0x5F + co2: + name: EE895 CO2 + temperature: + name: EE895 Temperature + pressure: + name: EE895 Pressure diff --git a/tests/components/ee895/test.esp8266.yaml b/tests/components/ee895/test.esp8266.yaml new file mode 100644 index 0000000000..4a36117fab --- /dev/null +++ b/tests/components/ee895/test.esp8266.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_ee895 + scl: 5 + sda: 4 + +sensor: + - platform: ee895 + address: 0x5F + co2: + name: EE895 CO2 + temperature: + name: EE895 Temperature + pressure: + name: EE895 Pressure diff --git a/tests/components/ee895/test.rp2040.yaml b/tests/components/ee895/test.rp2040.yaml new file mode 100644 index 0000000000..4a36117fab --- /dev/null +++ b/tests/components/ee895/test.rp2040.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_ee895 + scl: 5 + sda: 4 + +sensor: + - platform: ee895 + address: 0x5F + co2: + name: EE895 CO2 + temperature: + name: EE895 Temperature + pressure: + name: EE895 Pressure diff --git a/tests/components/ektf2232/test.esp32-c3-idf.yaml b/tests/components/ektf2232/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..371f2795a2 --- /dev/null +++ b/tests/components/ektf2232/test.esp32-c3-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ektf2232 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ektf2232 + interrupt_pin: 6 + rts_pin: 7 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ektf2232/test.esp32-c3.yaml b/tests/components/ektf2232/test.esp32-c3.yaml new file mode 100644 index 0000000000..371f2795a2 --- /dev/null +++ b/tests/components/ektf2232/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ektf2232 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ektf2232 + interrupt_pin: 6 + rts_pin: 7 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ektf2232/test.esp32-idf.yaml b/tests/components/ektf2232/test.esp32-idf.yaml new file mode 100644 index 0000000000..9c6eef8bb3 --- /dev/null +++ b/tests/components/ektf2232/test.esp32-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ektf2232 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ektf2232 + interrupt_pin: 14 + rts_pin: 15 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ektf2232/test.esp32.yaml b/tests/components/ektf2232/test.esp32.yaml new file mode 100644 index 0000000000..9c6eef8bb3 --- /dev/null +++ b/tests/components/ektf2232/test.esp32.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ektf2232 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ektf2232 + interrupt_pin: 14 + rts_pin: 15 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ektf2232/test.esp8266.yaml b/tests/components/ektf2232/test.esp8266.yaml new file mode 100644 index 0000000000..03f18f7184 --- /dev/null +++ b/tests/components/ektf2232/test.esp8266.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ektf2232 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ektf2232 + interrupt_pin: 12 + rts_pin: 13 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ektf2232/test.rp2040.yaml b/tests/components/ektf2232/test.rp2040.yaml new file mode 100644 index 0000000000..371f2795a2 --- /dev/null +++ b/tests/components/ektf2232/test.rp2040.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ektf2232 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ektf2232 + interrupt_pin: 6 + rts_pin: 7 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/emc2101/test.esp32-c3-idf.yaml b/tests/components/emc2101/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..d95bc1b001 --- /dev/null +++ b/tests/components/emc2101/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_emc2101 + scl: 5 + sda: 4 + +emc2101: + pwm: + resolution: 8 + +output: + - platform: emc2101 + id: fan_duty_cycle + +sensor: + - platform: emc2101 + internal_temperature: + id: internal_temperature_sensor + name: Internal Temperature Sensor + speed: + id: speed_sensor + name: Speed Sensor + duty_cycle: + id: duty_cycle_sensor + name: Duty Cycle Sensor + update_interval: 5s diff --git a/tests/components/emc2101/test.esp32-c3.yaml b/tests/components/emc2101/test.esp32-c3.yaml new file mode 100644 index 0000000000..d95bc1b001 --- /dev/null +++ b/tests/components/emc2101/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_emc2101 + scl: 5 + sda: 4 + +emc2101: + pwm: + resolution: 8 + +output: + - platform: emc2101 + id: fan_duty_cycle + +sensor: + - platform: emc2101 + internal_temperature: + id: internal_temperature_sensor + name: Internal Temperature Sensor + speed: + id: speed_sensor + name: Speed Sensor + duty_cycle: + id: duty_cycle_sensor + name: Duty Cycle Sensor + update_interval: 5s diff --git a/tests/components/emc2101/test.esp32-idf.yaml b/tests/components/emc2101/test.esp32-idf.yaml new file mode 100644 index 0000000000..34a7d22b71 --- /dev/null +++ b/tests/components/emc2101/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_emc2101 + scl: 16 + sda: 17 + +emc2101: + pwm: + resolution: 8 + +output: + - platform: emc2101 + id: fan_duty_cycle + +sensor: + - platform: emc2101 + internal_temperature: + id: internal_temperature_sensor + name: Internal Temperature Sensor + speed: + id: speed_sensor + name: Speed Sensor + duty_cycle: + id: duty_cycle_sensor + name: Duty Cycle Sensor + update_interval: 5s diff --git a/tests/components/emc2101/test.esp32.yaml b/tests/components/emc2101/test.esp32.yaml new file mode 100644 index 0000000000..34a7d22b71 --- /dev/null +++ b/tests/components/emc2101/test.esp32.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_emc2101 + scl: 16 + sda: 17 + +emc2101: + pwm: + resolution: 8 + +output: + - platform: emc2101 + id: fan_duty_cycle + +sensor: + - platform: emc2101 + internal_temperature: + id: internal_temperature_sensor + name: Internal Temperature Sensor + speed: + id: speed_sensor + name: Speed Sensor + duty_cycle: + id: duty_cycle_sensor + name: Duty Cycle Sensor + update_interval: 5s diff --git a/tests/components/emc2101/test.esp8266.yaml b/tests/components/emc2101/test.esp8266.yaml new file mode 100644 index 0000000000..d95bc1b001 --- /dev/null +++ b/tests/components/emc2101/test.esp8266.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_emc2101 + scl: 5 + sda: 4 + +emc2101: + pwm: + resolution: 8 + +output: + - platform: emc2101 + id: fan_duty_cycle + +sensor: + - platform: emc2101 + internal_temperature: + id: internal_temperature_sensor + name: Internal Temperature Sensor + speed: + id: speed_sensor + name: Speed Sensor + duty_cycle: + id: duty_cycle_sensor + name: Duty Cycle Sensor + update_interval: 5s diff --git a/tests/components/emc2101/test.rp2040.yaml b/tests/components/emc2101/test.rp2040.yaml new file mode 100644 index 0000000000..d95bc1b001 --- /dev/null +++ b/tests/components/emc2101/test.rp2040.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_emc2101 + scl: 5 + sda: 4 + +emc2101: + pwm: + resolution: 8 + +output: + - platform: emc2101 + id: fan_duty_cycle + +sensor: + - platform: emc2101 + internal_temperature: + id: internal_temperature_sensor + name: Internal Temperature Sensor + speed: + id: speed_sensor + name: Speed Sensor + duty_cycle: + id: duty_cycle_sensor + name: Duty Cycle Sensor + update_interval: 5s diff --git a/tests/components/emmeti/common.yaml b/tests/components/emmeti/common.yaml new file mode 100644 index 0000000000..ac4201e19b --- /dev/null +++ b/tests/components/emmeti/common.yaml @@ -0,0 +1,14 @@ +remote_transmitter: + id: tx + pin: ${remote_transmitter_pin} + carrier_duty_percent: 100% + +remote_receiver: + id: rcvr + pin: ${remote_receiver_pin} + +climate: + - platform: emmeti + name: Emmeti + receiver_id: rcvr + transmitter_id: tx diff --git a/tests/components/emmeti/test.esp32-idf.yaml b/tests/components/emmeti/test.esp32-idf.yaml new file mode 100644 index 0000000000..2689ff279e --- /dev/null +++ b/tests/components/emmeti/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + remote_transmitter_pin: GPIO33 + remote_receiver_pin: GPIO32 + +<<: !include common.yaml diff --git a/tests/components/emmeti/test.esp32.yaml b/tests/components/emmeti/test.esp32.yaml new file mode 100644 index 0000000000..2689ff279e --- /dev/null +++ b/tests/components/emmeti/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + remote_transmitter_pin: GPIO33 + remote_receiver_pin: GPIO32 + +<<: !include common.yaml diff --git a/tests/components/emmeti/test.esp8266.yaml b/tests/components/emmeti/test.esp8266.yaml new file mode 100644 index 0000000000..2fb00aea61 --- /dev/null +++ b/tests/components/emmeti/test.esp8266.yaml @@ -0,0 +1,5 @@ +substitutions: + remote_transmitter_pin: GPIO4 + remote_receiver_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/endstop/test.esp32-c3-idf.yaml b/tests/components/endstop/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..341fbf7260 --- /dev/null +++ b/tests/components/endstop/test.esp32-c3-idf.yaml @@ -0,0 +1,33 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: endstop + id: endstop_cover + name: Endstop Cover + stop_action: + - switch.turn_on: template_switch1 + open_endstop: bin1 + open_action: + - switch.turn_on: template_switch1 + open_duration: 5min + close_endstop: bin1 + close_action: + - switch.turn_on: template_switch2 + close_duration: 4.5min + max_duration: 10min diff --git a/tests/components/endstop/test.esp32-c3.yaml b/tests/components/endstop/test.esp32-c3.yaml new file mode 100644 index 0000000000..341fbf7260 --- /dev/null +++ b/tests/components/endstop/test.esp32-c3.yaml @@ -0,0 +1,33 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: endstop + id: endstop_cover + name: Endstop Cover + stop_action: + - switch.turn_on: template_switch1 + open_endstop: bin1 + open_action: + - switch.turn_on: template_switch1 + open_duration: 5min + close_endstop: bin1 + close_action: + - switch.turn_on: template_switch2 + close_duration: 4.5min + max_duration: 10min diff --git a/tests/components/endstop/test.esp32-idf.yaml b/tests/components/endstop/test.esp32-idf.yaml new file mode 100644 index 0000000000..341fbf7260 --- /dev/null +++ b/tests/components/endstop/test.esp32-idf.yaml @@ -0,0 +1,33 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: endstop + id: endstop_cover + name: Endstop Cover + stop_action: + - switch.turn_on: template_switch1 + open_endstop: bin1 + open_action: + - switch.turn_on: template_switch1 + open_duration: 5min + close_endstop: bin1 + close_action: + - switch.turn_on: template_switch2 + close_duration: 4.5min + max_duration: 10min diff --git a/tests/components/endstop/test.esp32.yaml b/tests/components/endstop/test.esp32.yaml new file mode 100644 index 0000000000..341fbf7260 --- /dev/null +++ b/tests/components/endstop/test.esp32.yaml @@ -0,0 +1,33 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: endstop + id: endstop_cover + name: Endstop Cover + stop_action: + - switch.turn_on: template_switch1 + open_endstop: bin1 + open_action: + - switch.turn_on: template_switch1 + open_duration: 5min + close_endstop: bin1 + close_action: + - switch.turn_on: template_switch2 + close_duration: 4.5min + max_duration: 10min diff --git a/tests/components/endstop/test.esp8266.yaml b/tests/components/endstop/test.esp8266.yaml new file mode 100644 index 0000000000..341fbf7260 --- /dev/null +++ b/tests/components/endstop/test.esp8266.yaml @@ -0,0 +1,33 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: endstop + id: endstop_cover + name: Endstop Cover + stop_action: + - switch.turn_on: template_switch1 + open_endstop: bin1 + open_action: + - switch.turn_on: template_switch1 + open_duration: 5min + close_endstop: bin1 + close_action: + - switch.turn_on: template_switch2 + close_duration: 4.5min + max_duration: 10min diff --git a/tests/components/endstop/test.rp2040.yaml b/tests/components/endstop/test.rp2040.yaml new file mode 100644 index 0000000000..341fbf7260 --- /dev/null +++ b/tests/components/endstop/test.rp2040.yaml @@ -0,0 +1,33 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: endstop + id: endstop_cover + name: Endstop Cover + stop_action: + - switch.turn_on: template_switch1 + open_endstop: bin1 + open_action: + - switch.turn_on: template_switch1 + open_duration: 5min + close_endstop: bin1 + close_action: + - switch.turn_on: template_switch2 + close_duration: 4.5min + max_duration: 10min diff --git a/tests/components/ens160/test.esp32-c3-idf.yaml b/tests/components/ens160/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..29f48e812f --- /dev/null +++ b/tests/components/ens160/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ens160 + scl: 5 + sda: 4 + +sensor: + - platform: ens160 + eco2: + name: ENS160 eCO2 + tvoc: + name: ENS160 Total Volatile Organic Compounds + aqi: + name: ENS160 Air Quality Index diff --git a/tests/components/ens160/test.esp32-c3.yaml b/tests/components/ens160/test.esp32-c3.yaml new file mode 100644 index 0000000000..29f48e812f --- /dev/null +++ b/tests/components/ens160/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ens160 + scl: 5 + sda: 4 + +sensor: + - platform: ens160 + eco2: + name: ENS160 eCO2 + tvoc: + name: ENS160 Total Volatile Organic Compounds + aqi: + name: ENS160 Air Quality Index diff --git a/tests/components/ens160/test.esp32-idf.yaml b/tests/components/ens160/test.esp32-idf.yaml new file mode 100644 index 0000000000..23f7674aef --- /dev/null +++ b/tests/components/ens160/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ens160 + scl: 16 + sda: 17 + +sensor: + - platform: ens160 + eco2: + name: ENS160 eCO2 + tvoc: + name: ENS160 Total Volatile Organic Compounds + aqi: + name: ENS160 Air Quality Index diff --git a/tests/components/ens160/test.esp32.yaml b/tests/components/ens160/test.esp32.yaml new file mode 100644 index 0000000000..23f7674aef --- /dev/null +++ b/tests/components/ens160/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ens160 + scl: 16 + sda: 17 + +sensor: + - platform: ens160 + eco2: + name: ENS160 eCO2 + tvoc: + name: ENS160 Total Volatile Organic Compounds + aqi: + name: ENS160 Air Quality Index diff --git a/tests/components/ens160/test.esp8266.yaml b/tests/components/ens160/test.esp8266.yaml new file mode 100644 index 0000000000..29f48e812f --- /dev/null +++ b/tests/components/ens160/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ens160 + scl: 5 + sda: 4 + +sensor: + - platform: ens160 + eco2: + name: ENS160 eCO2 + tvoc: + name: ENS160 Total Volatile Organic Compounds + aqi: + name: ENS160 Air Quality Index diff --git a/tests/components/ens160/test.rp2040.yaml b/tests/components/ens160/test.rp2040.yaml new file mode 100644 index 0000000000..29f48e812f --- /dev/null +++ b/tests/components/ens160/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ens160 + scl: 5 + sda: 4 + +sensor: + - platform: ens160 + eco2: + name: ENS160 eCO2 + tvoc: + name: ENS160 Total Volatile Organic Compounds + aqi: + name: ENS160 Air Quality Index diff --git a/tests/components/ens210/test.esp32-c3-idf.yaml b/tests/components/ens210/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..bacb71f9f8 --- /dev/null +++ b/tests/components/ens210/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ens210 + scl: 5 + sda: 4 + +sensor: + - platform: ens210 + temperature: + name: ENS210 Temperature + humidity: + name: ENS210 Humidity + update_interval: 15s diff --git a/tests/components/ens210/test.esp32-c3.yaml b/tests/components/ens210/test.esp32-c3.yaml new file mode 100644 index 0000000000..bacb71f9f8 --- /dev/null +++ b/tests/components/ens210/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ens210 + scl: 5 + sda: 4 + +sensor: + - platform: ens210 + temperature: + name: ENS210 Temperature + humidity: + name: ENS210 Humidity + update_interval: 15s diff --git a/tests/components/ens210/test.esp32-idf.yaml b/tests/components/ens210/test.esp32-idf.yaml new file mode 100644 index 0000000000..8b2d29cc25 --- /dev/null +++ b/tests/components/ens210/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ens210 + scl: 16 + sda: 17 + +sensor: + - platform: ens210 + temperature: + name: ENS210 Temperature + humidity: + name: ENS210 Humidity + update_interval: 15s diff --git a/tests/components/ens210/test.esp32.yaml b/tests/components/ens210/test.esp32.yaml new file mode 100644 index 0000000000..8b2d29cc25 --- /dev/null +++ b/tests/components/ens210/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ens210 + scl: 16 + sda: 17 + +sensor: + - platform: ens210 + temperature: + name: ENS210 Temperature + humidity: + name: ENS210 Humidity + update_interval: 15s diff --git a/tests/components/ens210/test.esp8266.yaml b/tests/components/ens210/test.esp8266.yaml new file mode 100644 index 0000000000..bacb71f9f8 --- /dev/null +++ b/tests/components/ens210/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ens210 + scl: 5 + sda: 4 + +sensor: + - platform: ens210 + temperature: + name: ENS210 Temperature + humidity: + name: ENS210 Humidity + update_interval: 15s diff --git a/tests/components/ens210/test.rp2040.yaml b/tests/components/ens210/test.rp2040.yaml new file mode 100644 index 0000000000..bacb71f9f8 --- /dev/null +++ b/tests/components/ens210/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ens210 + scl: 5 + sda: 4 + +sensor: + - platform: ens210 + temperature: + name: ENS210 Temperature + humidity: + name: ENS210 Humidity + update_interval: 15s diff --git a/tests/components/esp32_ble/test.esp32-c3-idf.yaml b/tests/components/esp32_ble/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..76b35fc8f8 --- /dev/null +++ b/tests/components/esp32_ble/test.esp32-c3-idf.yaml @@ -0,0 +1,2 @@ +esp32_ble: + io_capability: keyboard_only diff --git a/tests/components/esp32_ble/test.esp32-c3.yaml b/tests/components/esp32_ble/test.esp32-c3.yaml new file mode 100644 index 0000000000..76b35fc8f8 --- /dev/null +++ b/tests/components/esp32_ble/test.esp32-c3.yaml @@ -0,0 +1,2 @@ +esp32_ble: + io_capability: keyboard_only diff --git a/tests/components/esp32_ble/test.esp32-idf.yaml b/tests/components/esp32_ble/test.esp32-idf.yaml new file mode 100644 index 0000000000..76b35fc8f8 --- /dev/null +++ b/tests/components/esp32_ble/test.esp32-idf.yaml @@ -0,0 +1,2 @@ +esp32_ble: + io_capability: keyboard_only diff --git a/tests/components/esp32_ble/test.esp32.yaml b/tests/components/esp32_ble/test.esp32.yaml new file mode 100644 index 0000000000..76b35fc8f8 --- /dev/null +++ b/tests/components/esp32_ble/test.esp32.yaml @@ -0,0 +1,2 @@ +esp32_ble: + io_capability: keyboard_only diff --git a/tests/components/esp32_ble_beacon/test.esp32-c3-idf.yaml b/tests/components/esp32_ble_beacon/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..aafb0341d7 --- /dev/null +++ b/tests/components/esp32_ble_beacon/test.esp32-c3-idf.yaml @@ -0,0 +1,3 @@ +esp32_ble_beacon: + type: iBeacon + uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98' diff --git a/tests/components/esp32_ble_beacon/test.esp32-c3.yaml b/tests/components/esp32_ble_beacon/test.esp32-c3.yaml new file mode 100644 index 0000000000..aafb0341d7 --- /dev/null +++ b/tests/components/esp32_ble_beacon/test.esp32-c3.yaml @@ -0,0 +1,3 @@ +esp32_ble_beacon: + type: iBeacon + uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98' diff --git a/tests/components/esp32_ble_beacon/test.esp32-idf.yaml b/tests/components/esp32_ble_beacon/test.esp32-idf.yaml new file mode 100644 index 0000000000..aafb0341d7 --- /dev/null +++ b/tests/components/esp32_ble_beacon/test.esp32-idf.yaml @@ -0,0 +1,3 @@ +esp32_ble_beacon: + type: iBeacon + uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98' diff --git a/tests/components/esp32_ble_beacon/test.esp32.yaml b/tests/components/esp32_ble_beacon/test.esp32.yaml new file mode 100644 index 0000000000..aafb0341d7 --- /dev/null +++ b/tests/components/esp32_ble_beacon/test.esp32.yaml @@ -0,0 +1,3 @@ +esp32_ble_beacon: + type: iBeacon + uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98' diff --git a/tests/components/esp32_ble_client/test.esp32-c3-idf.yaml b/tests/components/esp32_ble_client/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..33b7205bf2 --- /dev/null +++ b/tests/components/esp32_ble_client/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: blec diff --git a/tests/components/esp32_ble_client/test.esp32-c3.yaml b/tests/components/esp32_ble_client/test.esp32-c3.yaml new file mode 100644 index 0000000000..33b7205bf2 --- /dev/null +++ b/tests/components/esp32_ble_client/test.esp32-c3.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: blec diff --git a/tests/components/esp32_ble_client/test.esp32-idf.yaml b/tests/components/esp32_ble_client/test.esp32-idf.yaml new file mode 100644 index 0000000000..33b7205bf2 --- /dev/null +++ b/tests/components/esp32_ble_client/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: blec diff --git a/tests/components/esp32_ble_client/test.esp32.yaml b/tests/components/esp32_ble_client/test.esp32.yaml new file mode 100644 index 0000000000..33b7205bf2 --- /dev/null +++ b/tests/components/esp32_ble_client/test.esp32.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: blec diff --git a/tests/components/esp32_ble_server/test.esp32-c3-idf.yaml b/tests/components/esp32_ble_server/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..29a5407f84 --- /dev/null +++ b/tests/components/esp32_ble_server/test.esp32-c3-idf.yaml @@ -0,0 +1,3 @@ +esp32_ble_server: + id: ble + manufacturer_data: [0x72, 0x4, 0x00, 0x23] diff --git a/tests/components/esp32_ble_server/test.esp32-c3.yaml b/tests/components/esp32_ble_server/test.esp32-c3.yaml new file mode 100644 index 0000000000..29a5407f84 --- /dev/null +++ b/tests/components/esp32_ble_server/test.esp32-c3.yaml @@ -0,0 +1,3 @@ +esp32_ble_server: + id: ble + manufacturer_data: [0x72, 0x4, 0x00, 0x23] diff --git a/tests/components/esp32_ble_server/test.esp32-idf.yaml b/tests/components/esp32_ble_server/test.esp32-idf.yaml new file mode 100644 index 0000000000..29a5407f84 --- /dev/null +++ b/tests/components/esp32_ble_server/test.esp32-idf.yaml @@ -0,0 +1,3 @@ +esp32_ble_server: + id: ble + manufacturer_data: [0x72, 0x4, 0x00, 0x23] diff --git a/tests/components/esp32_ble_server/test.esp32.yaml b/tests/components/esp32_ble_server/test.esp32.yaml new file mode 100644 index 0000000000..29a5407f84 --- /dev/null +++ b/tests/components/esp32_ble_server/test.esp32.yaml @@ -0,0 +1,3 @@ +esp32_ble_server: + id: ble + manufacturer_data: [0x72, 0x4, 0x00, 0x23] diff --git a/tests/components/esp32_ble_tracker/test.esp32-c3-idf.yaml b/tests/components/esp32_ble_tracker/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ef23635c9e --- /dev/null +++ b/tests/components/esp32_ble_tracker/test.esp32-c3-idf.yaml @@ -0,0 +1,41 @@ +esphome: + on_boot: + then: + - esp32_ble_tracker.start_scan + - esp32_ble_tracker.stop_scan + +esp32_ble_tracker: + on_ble_advertise: + - mac_address: + - AA:BB:CC:DD:EE:FF + - FF:EE:DD:CC:BB:AA + then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address (%s) exists in list", x.address_str().c_str()); + # yamllint enable rule:line-length + - mac_address: AC:37:43:77:5F:4C + then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + # yamllint enable rule:line-length + - then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + # yamllint enable rule:line-length + on_ble_service_data_advertise: + - service_uuid: ABCD + then: + - lambda: !lambda |- + ESP_LOGD("main", "Length of service data is %i", x.size()); + on_ble_manufacturer_data_advertise: + - manufacturer_id: ABCD + then: + - lambda: !lambda |- + ESP_LOGD("main", "Length of manufacturer data is %i", x.size()); + on_scan_end: + - then: + - lambda: |- + ESP_LOGD("ble_auto", "The scan has ended!"); diff --git a/tests/components/esp32_ble_tracker/test.esp32-c3.yaml b/tests/components/esp32_ble_tracker/test.esp32-c3.yaml new file mode 100644 index 0000000000..ef23635c9e --- /dev/null +++ b/tests/components/esp32_ble_tracker/test.esp32-c3.yaml @@ -0,0 +1,41 @@ +esphome: + on_boot: + then: + - esp32_ble_tracker.start_scan + - esp32_ble_tracker.stop_scan + +esp32_ble_tracker: + on_ble_advertise: + - mac_address: + - AA:BB:CC:DD:EE:FF + - FF:EE:DD:CC:BB:AA + then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address (%s) exists in list", x.address_str().c_str()); + # yamllint enable rule:line-length + - mac_address: AC:37:43:77:5F:4C + then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + # yamllint enable rule:line-length + - then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + # yamllint enable rule:line-length + on_ble_service_data_advertise: + - service_uuid: ABCD + then: + - lambda: !lambda |- + ESP_LOGD("main", "Length of service data is %i", x.size()); + on_ble_manufacturer_data_advertise: + - manufacturer_id: ABCD + then: + - lambda: !lambda |- + ESP_LOGD("main", "Length of manufacturer data is %i", x.size()); + on_scan_end: + - then: + - lambda: |- + ESP_LOGD("ble_auto", "The scan has ended!"); diff --git a/tests/components/esp32_ble_tracker/test.esp32-idf.yaml b/tests/components/esp32_ble_tracker/test.esp32-idf.yaml new file mode 100644 index 0000000000..ef23635c9e --- /dev/null +++ b/tests/components/esp32_ble_tracker/test.esp32-idf.yaml @@ -0,0 +1,41 @@ +esphome: + on_boot: + then: + - esp32_ble_tracker.start_scan + - esp32_ble_tracker.stop_scan + +esp32_ble_tracker: + on_ble_advertise: + - mac_address: + - AA:BB:CC:DD:EE:FF + - FF:EE:DD:CC:BB:AA + then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address (%s) exists in list", x.address_str().c_str()); + # yamllint enable rule:line-length + - mac_address: AC:37:43:77:5F:4C + then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + # yamllint enable rule:line-length + - then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + # yamllint enable rule:line-length + on_ble_service_data_advertise: + - service_uuid: ABCD + then: + - lambda: !lambda |- + ESP_LOGD("main", "Length of service data is %i", x.size()); + on_ble_manufacturer_data_advertise: + - manufacturer_id: ABCD + then: + - lambda: !lambda |- + ESP_LOGD("main", "Length of manufacturer data is %i", x.size()); + on_scan_end: + - then: + - lambda: |- + ESP_LOGD("ble_auto", "The scan has ended!"); diff --git a/tests/components/esp32_ble_tracker/test.esp32.yaml b/tests/components/esp32_ble_tracker/test.esp32.yaml new file mode 100644 index 0000000000..ef23635c9e --- /dev/null +++ b/tests/components/esp32_ble_tracker/test.esp32.yaml @@ -0,0 +1,41 @@ +esphome: + on_boot: + then: + - esp32_ble_tracker.start_scan + - esp32_ble_tracker.stop_scan + +esp32_ble_tracker: + on_ble_advertise: + - mac_address: + - AA:BB:CC:DD:EE:FF + - FF:EE:DD:CC:BB:AA + then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address (%s) exists in list", x.address_str().c_str()); + # yamllint enable rule:line-length + - mac_address: AC:37:43:77:5F:4C + then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + # yamllint enable rule:line-length + - then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + # yamllint enable rule:line-length + on_ble_service_data_advertise: + - service_uuid: ABCD + then: + - lambda: !lambda |- + ESP_LOGD("main", "Length of service data is %i", x.size()); + on_ble_manufacturer_data_advertise: + - manufacturer_id: ABCD + then: + - lambda: !lambda |- + ESP_LOGD("main", "Length of manufacturer data is %i", x.size()); + on_scan_end: + - then: + - lambda: |- + ESP_LOGD("ble_auto", "The scan has ended!"); diff --git a/tests/components/esp32_camera/test.esp32-idf.yaml b/tests/components/esp32_camera/test.esp32-idf.yaml new file mode 100644 index 0000000000..2f5f792f1c --- /dev/null +++ b/tests/components/esp32_camera/test.esp32-idf.yaml @@ -0,0 +1,28 @@ +esp32_camera: + name: ESP32 Camera + data_pins: + - number: 17 + - number: 35 + - number: 34 + - number: 5 + - number: 39 + - number: 18 + - number: 36 + - number: 19 + vsync_pin: 22 + href_pin: 26 + pixel_clock_pin: 21 + external_clock: + pin: 27 + frequency: 20MHz + i2c_pins: + sda: 25 + scl: 23 + reset_pin: 15 + power_down_pin: 1 + resolution: 640x480 + jpeg_quality: 10 + on_image: + then: + - lambda: |- + ESP_LOGD("main", "image len=%d, data=%c", image.length, image.data[0]); diff --git a/tests/components/esp32_camera/test.esp32.yaml b/tests/components/esp32_camera/test.esp32.yaml new file mode 100644 index 0000000000..2f5f792f1c --- /dev/null +++ b/tests/components/esp32_camera/test.esp32.yaml @@ -0,0 +1,28 @@ +esp32_camera: + name: ESP32 Camera + data_pins: + - number: 17 + - number: 35 + - number: 34 + - number: 5 + - number: 39 + - number: 18 + - number: 36 + - number: 19 + vsync_pin: 22 + href_pin: 26 + pixel_clock_pin: 21 + external_clock: + pin: 27 + frequency: 20MHz + i2c_pins: + sda: 25 + scl: 23 + reset_pin: 15 + power_down_pin: 1 + resolution: 640x480 + jpeg_quality: 10 + on_image: + then: + - lambda: |- + ESP_LOGD("main", "image len=%d, data=%c", image.length, image.data[0]); diff --git a/tests/components/esp32_camera_web_server/test.esp32-idf.yaml b/tests/components/esp32_camera_web_server/test.esp32-idf.yaml new file mode 100644 index 0000000000..5edefdf0a8 --- /dev/null +++ b/tests/components/esp32_camera_web_server/test.esp32-idf.yaml @@ -0,0 +1,34 @@ +esp32_camera: + name: ESP32 Camera + data_pins: + - number: 17 + - number: 35 + - number: 34 + - number: 5 + - number: 39 + - number: 18 + - number: 36 + - number: 19 + vsync_pin: 22 + href_pin: 26 + pixel_clock_pin: 21 + external_clock: + pin: 27 + frequency: 20MHz + i2c_pins: + sda: 25 + scl: 23 + reset_pin: 15 + power_down_pin: 1 + resolution: 640x480 + jpeg_quality: 10 + on_image: + then: + - lambda: |- + ESP_LOGD("main", "image len=%d, data=%c", image.length, image.data[0]); + +esp32_camera_web_server: + - port: 8080 + mode: stream + - port: 8081 + mode: snapshot diff --git a/tests/components/esp32_camera_web_server/test.esp32.yaml b/tests/components/esp32_camera_web_server/test.esp32.yaml new file mode 100644 index 0000000000..5edefdf0a8 --- /dev/null +++ b/tests/components/esp32_camera_web_server/test.esp32.yaml @@ -0,0 +1,34 @@ +esp32_camera: + name: ESP32 Camera + data_pins: + - number: 17 + - number: 35 + - number: 34 + - number: 5 + - number: 39 + - number: 18 + - number: 36 + - number: 19 + vsync_pin: 22 + href_pin: 26 + pixel_clock_pin: 21 + external_clock: + pin: 27 + frequency: 20MHz + i2c_pins: + sda: 25 + scl: 23 + reset_pin: 15 + power_down_pin: 1 + resolution: 640x480 + jpeg_quality: 10 + on_image: + then: + - lambda: |- + ESP_LOGD("main", "image len=%d, data=%c", image.length, image.data[0]); + +esp32_camera_web_server: + - port: 8080 + mode: stream + - port: 8081 + mode: snapshot diff --git a/tests/components/esp32_can/test.esp32-c3-idf.yaml b/tests/components/esp32_can/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..b4fd34cf51 --- /dev/null +++ b/tests/components/esp32_can/test.esp32-c3-idf.yaml @@ -0,0 +1,45 @@ +esphome: + on_boot: + then: + - canbus.send: + # Extended ID explicit + use_extended_id: true + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + - canbus.send: + # Standard ID by default + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: 4 + tx_pin: 5 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", b.c_str() ); + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/esp32_can/test.esp32-c3.yaml b/tests/components/esp32_can/test.esp32-c3.yaml new file mode 100644 index 0000000000..b4fd34cf51 --- /dev/null +++ b/tests/components/esp32_can/test.esp32-c3.yaml @@ -0,0 +1,45 @@ +esphome: + on_boot: + then: + - canbus.send: + # Extended ID explicit + use_extended_id: true + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + - canbus.send: + # Standard ID by default + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: 4 + tx_pin: 5 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", b.c_str() ); + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/esp32_can/test.esp32-idf.yaml b/tests/components/esp32_can/test.esp32-idf.yaml new file mode 100644 index 0000000000..159a695853 --- /dev/null +++ b/tests/components/esp32_can/test.esp32-idf.yaml @@ -0,0 +1,45 @@ +esphome: + on_boot: + then: + - canbus.send: + # Extended ID explicit + use_extended_id: true + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + - canbus.send: + # Standard ID by default + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: 13 + tx_pin: 14 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", b.c_str() ); + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/esp32_can/test.esp32.yaml b/tests/components/esp32_can/test.esp32.yaml new file mode 100644 index 0000000000..159a695853 --- /dev/null +++ b/tests/components/esp32_can/test.esp32.yaml @@ -0,0 +1,45 @@ +esphome: + on_boot: + then: + - canbus.send: + # Extended ID explicit + use_extended_id: true + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + - canbus.send: + # Standard ID by default + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: 13 + tx_pin: 14 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", b.c_str() ); + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/esp32_dac/test.esp32-idf.yaml b/tests/components/esp32_dac/test.esp32-idf.yaml new file mode 100644 index 0000000000..225627f5af --- /dev/null +++ b/tests/components/esp32_dac/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +output: + - platform: esp32_dac + id: dac_output + pin: 25 diff --git a/tests/components/esp32_dac/test.esp32.yaml b/tests/components/esp32_dac/test.esp32.yaml new file mode 100644 index 0000000000..225627f5af --- /dev/null +++ b/tests/components/esp32_dac/test.esp32.yaml @@ -0,0 +1,4 @@ +output: + - platform: esp32_dac + id: dac_output + pin: 25 diff --git a/tests/components/esp32_hall/test.esp32-idf.yaml b/tests/components/esp32_hall/test.esp32-idf.yaml new file mode 100644 index 0000000000..f8429f5aa0 --- /dev/null +++ b/tests/components/esp32_hall/test.esp32-idf.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: esp32_hall + name: ESP32 Hall Sensor diff --git a/tests/components/esp32_hall/test.esp32.yaml b/tests/components/esp32_hall/test.esp32.yaml new file mode 100644 index 0000000000..f8429f5aa0 --- /dev/null +++ b/tests/components/esp32_hall/test.esp32.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: esp32_hall + name: ESP32 Hall Sensor diff --git a/tests/components/esp32_improv/test.esp32-c3-idf.yaml b/tests/components/esp32_improv/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..7eb3f9c0be --- /dev/null +++ b/tests/components/esp32_improv/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +binary_sensor: + - platform: gpio + pin: 0 + id: io0_button + +output: + - platform: gpio + pin: 2 + id: built_in_led + +esp32_improv: + authorizer: io0_button + authorized_duration: 1min + status_indicator: built_in_led diff --git a/tests/components/esp32_improv/test.esp32-c3.yaml b/tests/components/esp32_improv/test.esp32-c3.yaml new file mode 100644 index 0000000000..7eb3f9c0be --- /dev/null +++ b/tests/components/esp32_improv/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +binary_sensor: + - platform: gpio + pin: 0 + id: io0_button + +output: + - platform: gpio + pin: 2 + id: built_in_led + +esp32_improv: + authorizer: io0_button + authorized_duration: 1min + status_indicator: built_in_led diff --git a/tests/components/esp32_improv/test.esp32-idf.yaml b/tests/components/esp32_improv/test.esp32-idf.yaml new file mode 100644 index 0000000000..7eb3f9c0be --- /dev/null +++ b/tests/components/esp32_improv/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +binary_sensor: + - platform: gpio + pin: 0 + id: io0_button + +output: + - platform: gpio + pin: 2 + id: built_in_led + +esp32_improv: + authorizer: io0_button + authorized_duration: 1min + status_indicator: built_in_led diff --git a/tests/components/esp32_improv/test.esp32.yaml b/tests/components/esp32_improv/test.esp32.yaml new file mode 100644 index 0000000000..7eb3f9c0be --- /dev/null +++ b/tests/components/esp32_improv/test.esp32.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +binary_sensor: + - platform: gpio + pin: 0 + id: io0_button + +output: + - platform: gpio + pin: 2 + id: built_in_led + +esp32_improv: + authorizer: io0_button + authorized_duration: 1min + status_indicator: built_in_led diff --git a/tests/components/esp32_rmt_led_strip/test.esp32-c3-idf.yaml b/tests/components/esp32_rmt_led_strip/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..b226d1de06 --- /dev/null +++ b/tests/components/esp32_rmt_led_strip/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +light: + - platform: esp32_rmt_led_strip + id: led_strip + pin: 4 + num_leds: 60 + rmt_channel: 0 + rgb_order: GRB + chipset: ws2812 + - platform: esp32_rmt_led_strip + id: led_strip2 + pin: 5 + num_leds: 60 + rmt_channel: 1 + rgb_order: RGB + bit0_high: 100us + bit0_low: 100us + bit1_high: 100us + bit1_low: 100us diff --git a/tests/components/esp32_rmt_led_strip/test.esp32-c3.yaml b/tests/components/esp32_rmt_led_strip/test.esp32-c3.yaml new file mode 100644 index 0000000000..b226d1de06 --- /dev/null +++ b/tests/components/esp32_rmt_led_strip/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +light: + - platform: esp32_rmt_led_strip + id: led_strip + pin: 4 + num_leds: 60 + rmt_channel: 0 + rgb_order: GRB + chipset: ws2812 + - platform: esp32_rmt_led_strip + id: led_strip2 + pin: 5 + num_leds: 60 + rmt_channel: 1 + rgb_order: RGB + bit0_high: 100us + bit0_low: 100us + bit1_high: 100us + bit1_low: 100us diff --git a/tests/components/esp32_rmt_led_strip/test.esp32-idf.yaml b/tests/components/esp32_rmt_led_strip/test.esp32-idf.yaml new file mode 100644 index 0000000000..d51a66451f --- /dev/null +++ b/tests/components/esp32_rmt_led_strip/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +light: + - platform: esp32_rmt_led_strip + id: led_strip + pin: 13 + num_leds: 60 + rmt_channel: 6 + rgb_order: GRB + chipset: ws2812 + - platform: esp32_rmt_led_strip + id: led_strip2 + pin: 14 + num_leds: 60 + rmt_channel: 2 + rgb_order: RGB + bit0_high: 100us + bit0_low: 100us + bit1_high: 100us + bit1_low: 100us diff --git a/tests/components/esp32_rmt_led_strip/test.esp32.yaml b/tests/components/esp32_rmt_led_strip/test.esp32.yaml new file mode 100644 index 0000000000..d51a66451f --- /dev/null +++ b/tests/components/esp32_rmt_led_strip/test.esp32.yaml @@ -0,0 +1,18 @@ +light: + - platform: esp32_rmt_led_strip + id: led_strip + pin: 13 + num_leds: 60 + rmt_channel: 6 + rgb_order: GRB + chipset: ws2812 + - platform: esp32_rmt_led_strip + id: led_strip2 + pin: 14 + num_leds: 60 + rmt_channel: 2 + rgb_order: RGB + bit0_high: 100us + bit0_low: 100us + bit1_high: 100us + bit1_low: 100us diff --git a/tests/components/esp32_touch/test.esp32-idf.yaml b/tests/components/esp32_touch/test.esp32-idf.yaml new file mode 100644 index 0000000000..691cce8d86 --- /dev/null +++ b/tests/components/esp32_touch/test.esp32-idf.yaml @@ -0,0 +1,16 @@ +esp32_touch: + setup_mode: false + iir_filter: 10ms + sleep_duration: 27ms + measurement_duration: 8ms + low_voltage_reference: 0.5V + high_voltage_reference: 2.7V + voltage_attenuation: 1.5V + +binary_sensor: + - platform: esp32_touch + name: ESP32 Touch Pad + pin: 27 + threshold: 1000 + on_press: + - logger.log: "I'm touched!" diff --git a/tests/components/esp32_touch/test.esp32.yaml b/tests/components/esp32_touch/test.esp32.yaml new file mode 100644 index 0000000000..691cce8d86 --- /dev/null +++ b/tests/components/esp32_touch/test.esp32.yaml @@ -0,0 +1,16 @@ +esp32_touch: + setup_mode: false + iir_filter: 10ms + sleep_duration: 27ms + measurement_duration: 8ms + low_voltage_reference: 0.5V + high_voltage_reference: 2.7V + voltage_attenuation: 1.5V + +binary_sensor: + - platform: esp32_touch + name: ESP32 Touch Pad + pin: 27 + threshold: 1000 + on_press: + - logger.log: "I'm touched!" diff --git a/tests/components/esp8266_pwm/test.esp8266.yaml b/tests/components/esp8266_pwm/test.esp8266.yaml new file mode 100644 index 0000000000..52b290f91b --- /dev/null +++ b/tests/components/esp8266_pwm/test.esp8266.yaml @@ -0,0 +1,8 @@ +output: + - platform: esp8266_pwm + id: out + pin: 4 + frequency: 50Hz + - platform: esp8266_pwm + id: out2 + pin: 5 diff --git a/tests/components/ethernet/lan8720.esp32-idf.yaml b/tests/components/ethernet/lan8720.esp32-idf.yaml new file mode 100644 index 0000000000..b9ed9cb036 --- /dev/null +++ b/tests/components/ethernet/lan8720.esp32-idf.yaml @@ -0,0 +1,12 @@ +ethernet: + type: LAN8720 + mdc_pin: 23 + mdio_pin: 25 + clk_mode: GPIO0_IN + phy_addr: 0 + power_pin: 26 + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local diff --git a/tests/components/ethernet/lan8720.esp32.yaml b/tests/components/ethernet/lan8720.esp32.yaml new file mode 100644 index 0000000000..b9ed9cb036 --- /dev/null +++ b/tests/components/ethernet/lan8720.esp32.yaml @@ -0,0 +1,12 @@ +ethernet: + type: LAN8720 + mdc_pin: 23 + mdio_pin: 25 + clk_mode: GPIO0_IN + phy_addr: 0 + power_pin: 26 + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local diff --git a/tests/components/ethernet/test.esp32-idf.yaml b/tests/components/ethernet/test.esp32-idf.yaml new file mode 100644 index 0000000000..b9ed9cb036 --- /dev/null +++ b/tests/components/ethernet/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +ethernet: + type: LAN8720 + mdc_pin: 23 + mdio_pin: 25 + clk_mode: GPIO0_IN + phy_addr: 0 + power_pin: 26 + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local diff --git a/tests/components/ethernet/test.esp32.yaml b/tests/components/ethernet/test.esp32.yaml new file mode 100644 index 0000000000..b9ed9cb036 --- /dev/null +++ b/tests/components/ethernet/test.esp32.yaml @@ -0,0 +1,12 @@ +ethernet: + type: LAN8720 + mdc_pin: 23 + mdio_pin: 25 + clk_mode: GPIO0_IN + phy_addr: 0 + power_pin: 26 + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local diff --git a/tests/components/ethernet/w5500.esp32-idf.yaml b/tests/components/ethernet/w5500.esp32-idf.yaml new file mode 100644 index 0000000000..6fdccb36e3 --- /dev/null +++ b/tests/components/ethernet/w5500.esp32-idf.yaml @@ -0,0 +1,14 @@ +ethernet: + type: W5500 + clk_pin: GPIO19 + mosi_pin: GPIO21 + miso_pin: GPIO23 + cs_pin: GPIO18 + interrupt_pin: GPIO36 + reset_pin: GPIO22 + clock_speed: 10Mhz + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local diff --git a/tests/components/ethernet/w5500.esp32.yaml b/tests/components/ethernet/w5500.esp32.yaml new file mode 100644 index 0000000000..6fdccb36e3 --- /dev/null +++ b/tests/components/ethernet/w5500.esp32.yaml @@ -0,0 +1,14 @@ +ethernet: + type: W5500 + clk_pin: GPIO19 + mosi_pin: GPIO21 + miso_pin: GPIO23 + cs_pin: GPIO18 + interrupt_pin: GPIO36 + reset_pin: GPIO22 + clock_speed: 10Mhz + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local diff --git a/tests/components/ethernet_info/test.esp32-idf.yaml b/tests/components/ethernet_info/test.esp32-idf.yaml new file mode 100644 index 0000000000..c5da2bb666 --- /dev/null +++ b/tests/components/ethernet_info/test.esp32-idf.yaml @@ -0,0 +1,17 @@ +ethernet: + type: LAN8720 + mdc_pin: 23 + mdio_pin: 25 + clk_mode: GPIO0_IN + phy_addr: 0 + power_pin: 26 + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local + +text_sensor: + - platform: ethernet_info + ip_address: + name: IP Address diff --git a/tests/components/ethernet_info/test.esp32.yaml b/tests/components/ethernet_info/test.esp32.yaml new file mode 100644 index 0000000000..c5da2bb666 --- /dev/null +++ b/tests/components/ethernet_info/test.esp32.yaml @@ -0,0 +1,17 @@ +ethernet: + type: LAN8720 + mdc_pin: 23 + mdio_pin: 25 + clk_mode: GPIO0_IN + phy_addr: 0 + power_pin: 26 + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local + +text_sensor: + - platform: ethernet_info + ip_address: + name: IP Address diff --git a/tests/components/exposure_notifications/test.esp32-c3-idf.yaml b/tests/components/exposure_notifications/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..faba5bb2d1 --- /dev/null +++ b/tests/components/exposure_notifications/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +exposure_notifications: + on_exposure_notification: + then: + - lambda: | + ESP_LOGD("main", "Got notification:"); + ESP_LOGD("main", " RPI: %s", format_hex(x.rolling_proximity_identifier).c_str()); + ESP_LOGD("main", " RSSI: %d", x.rssi); diff --git a/tests/components/exposure_notifications/test.esp32-c3.yaml b/tests/components/exposure_notifications/test.esp32-c3.yaml new file mode 100644 index 0000000000..faba5bb2d1 --- /dev/null +++ b/tests/components/exposure_notifications/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +exposure_notifications: + on_exposure_notification: + then: + - lambda: | + ESP_LOGD("main", "Got notification:"); + ESP_LOGD("main", " RPI: %s", format_hex(x.rolling_proximity_identifier).c_str()); + ESP_LOGD("main", " RSSI: %d", x.rssi); diff --git a/tests/components/exposure_notifications/test.esp32-idf.yaml b/tests/components/exposure_notifications/test.esp32-idf.yaml new file mode 100644 index 0000000000..faba5bb2d1 --- /dev/null +++ b/tests/components/exposure_notifications/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +exposure_notifications: + on_exposure_notification: + then: + - lambda: | + ESP_LOGD("main", "Got notification:"); + ESP_LOGD("main", " RPI: %s", format_hex(x.rolling_proximity_identifier).c_str()); + ESP_LOGD("main", " RSSI: %d", x.rssi); diff --git a/tests/components/exposure_notifications/test.esp32.yaml b/tests/components/exposure_notifications/test.esp32.yaml new file mode 100644 index 0000000000..faba5bb2d1 --- /dev/null +++ b/tests/components/exposure_notifications/test.esp32.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +exposure_notifications: + on_exposure_notification: + then: + - lambda: | + ESP_LOGD("main", "Got notification:"); + ESP_LOGD("main", " RPI: %s", format_hex(x.rolling_proximity_identifier).c_str()); + ESP_LOGD("main", " RSSI: %d", x.rssi); diff --git a/tests/components/external_components/test.esp32-c3-idf.yaml b/tests/components/external_components/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..2b51267ec6 --- /dev/null +++ b/tests/components/external_components/test.esp32-c3-idf.yaml @@ -0,0 +1,6 @@ +external_components: + - source: github://esphome/esphome@dev + refresh: 1d + components: [bh1750] + - source: ../../../esphome/components + components: [sntp] diff --git a/tests/components/external_components/test.esp32-c3.yaml b/tests/components/external_components/test.esp32-c3.yaml new file mode 100644 index 0000000000..2b51267ec6 --- /dev/null +++ b/tests/components/external_components/test.esp32-c3.yaml @@ -0,0 +1,6 @@ +external_components: + - source: github://esphome/esphome@dev + refresh: 1d + components: [bh1750] + - source: ../../../esphome/components + components: [sntp] diff --git a/tests/components/external_components/test.esp32-idf.yaml b/tests/components/external_components/test.esp32-idf.yaml new file mode 100644 index 0000000000..2b51267ec6 --- /dev/null +++ b/tests/components/external_components/test.esp32-idf.yaml @@ -0,0 +1,6 @@ +external_components: + - source: github://esphome/esphome@dev + refresh: 1d + components: [bh1750] + - source: ../../../esphome/components + components: [sntp] diff --git a/tests/components/external_components/test.esp32.yaml b/tests/components/external_components/test.esp32.yaml new file mode 100644 index 0000000000..2b51267ec6 --- /dev/null +++ b/tests/components/external_components/test.esp32.yaml @@ -0,0 +1,6 @@ +external_components: + - source: github://esphome/esphome@dev + refresh: 1d + components: [bh1750] + - source: ../../../esphome/components + components: [sntp] diff --git a/tests/components/external_components/test.esp8266.yaml b/tests/components/external_components/test.esp8266.yaml new file mode 100644 index 0000000000..2b51267ec6 --- /dev/null +++ b/tests/components/external_components/test.esp8266.yaml @@ -0,0 +1,6 @@ +external_components: + - source: github://esphome/esphome@dev + refresh: 1d + components: [bh1750] + - source: ../../../esphome/components + components: [sntp] diff --git a/tests/components/external_components/test.rp2040.yaml b/tests/components/external_components/test.rp2040.yaml new file mode 100644 index 0000000000..2b51267ec6 --- /dev/null +++ b/tests/components/external_components/test.rp2040.yaml @@ -0,0 +1,6 @@ +external_components: + - source: github://esphome/esphome@dev + refresh: 1d + components: [bh1750] + - source: ../../../esphome/components + components: [sntp] diff --git a/tests/components/ezo/test.esp32-c3-idf.yaml b/tests/components/ezo/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..93ceb9efd3 --- /dev/null +++ b/tests/components/ezo/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_ezo + scl: 5 + sda: 4 + +sensor: + - platform: ezo + id: ph_ezo + address: 99 + unit_of_measurement: pH diff --git a/tests/components/ezo/test.esp32-c3.yaml b/tests/components/ezo/test.esp32-c3.yaml new file mode 100644 index 0000000000..93ceb9efd3 --- /dev/null +++ b/tests/components/ezo/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_ezo + scl: 5 + sda: 4 + +sensor: + - platform: ezo + id: ph_ezo + address: 99 + unit_of_measurement: pH diff --git a/tests/components/ezo/test.esp32-idf.yaml b/tests/components/ezo/test.esp32-idf.yaml new file mode 100644 index 0000000000..61a8d2b25f --- /dev/null +++ b/tests/components/ezo/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_ezo + scl: 16 + sda: 17 + +sensor: + - platform: ezo + id: ph_ezo + address: 99 + unit_of_measurement: pH diff --git a/tests/components/ezo/test.esp32.yaml b/tests/components/ezo/test.esp32.yaml new file mode 100644 index 0000000000..61a8d2b25f --- /dev/null +++ b/tests/components/ezo/test.esp32.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_ezo + scl: 16 + sda: 17 + +sensor: + - platform: ezo + id: ph_ezo + address: 99 + unit_of_measurement: pH diff --git a/tests/components/ezo/test.esp8266.yaml b/tests/components/ezo/test.esp8266.yaml new file mode 100644 index 0000000000..93ceb9efd3 --- /dev/null +++ b/tests/components/ezo/test.esp8266.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_ezo + scl: 5 + sda: 4 + +sensor: + - platform: ezo + id: ph_ezo + address: 99 + unit_of_measurement: pH diff --git a/tests/components/ezo/test.rp2040.yaml b/tests/components/ezo/test.rp2040.yaml new file mode 100644 index 0000000000..93ceb9efd3 --- /dev/null +++ b/tests/components/ezo/test.rp2040.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_ezo + scl: 5 + sda: 4 + +sensor: + - platform: ezo + id: ph_ezo + address: 99 + unit_of_measurement: pH diff --git a/tests/components/ezo_pmp/test.esp32-c3-idf.yaml b/tests/components/ezo_pmp/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..fa047de3de --- /dev/null +++ b/tests/components/ezo_pmp/test.esp32-c3-idf.yaml @@ -0,0 +1,61 @@ +i2c: + - id: i2c_ezo_pmp + scl: 5 + sda: 4 + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +binary_sensor: + - platform: ezo_pmp + pump_state: + name: Pump State + is_paused: + name: Is Paused + +sensor: + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? diff --git a/tests/components/ezo_pmp/test.esp32-c3.yaml b/tests/components/ezo_pmp/test.esp32-c3.yaml new file mode 100644 index 0000000000..fa047de3de --- /dev/null +++ b/tests/components/ezo_pmp/test.esp32-c3.yaml @@ -0,0 +1,61 @@ +i2c: + - id: i2c_ezo_pmp + scl: 5 + sda: 4 + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +binary_sensor: + - platform: ezo_pmp + pump_state: + name: Pump State + is_paused: + name: Is Paused + +sensor: + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? diff --git a/tests/components/ezo_pmp/test.esp32-idf.yaml b/tests/components/ezo_pmp/test.esp32-idf.yaml new file mode 100644 index 0000000000..9fc929b365 --- /dev/null +++ b/tests/components/ezo_pmp/test.esp32-idf.yaml @@ -0,0 +1,61 @@ +i2c: + - id: i2c_ezo_pmp + scl: 16 + sda: 17 + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +binary_sensor: + - platform: ezo_pmp + pump_state: + name: Pump State + is_paused: + name: Is Paused + +sensor: + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? diff --git a/tests/components/ezo_pmp/test.esp32.yaml b/tests/components/ezo_pmp/test.esp32.yaml new file mode 100644 index 0000000000..9fc929b365 --- /dev/null +++ b/tests/components/ezo_pmp/test.esp32.yaml @@ -0,0 +1,61 @@ +i2c: + - id: i2c_ezo_pmp + scl: 16 + sda: 17 + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +binary_sensor: + - platform: ezo_pmp + pump_state: + name: Pump State + is_paused: + name: Is Paused + +sensor: + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? diff --git a/tests/components/ezo_pmp/test.esp8266.yaml b/tests/components/ezo_pmp/test.esp8266.yaml new file mode 100644 index 0000000000..fa047de3de --- /dev/null +++ b/tests/components/ezo_pmp/test.esp8266.yaml @@ -0,0 +1,61 @@ +i2c: + - id: i2c_ezo_pmp + scl: 5 + sda: 4 + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +binary_sensor: + - platform: ezo_pmp + pump_state: + name: Pump State + is_paused: + name: Is Paused + +sensor: + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? diff --git a/tests/components/ezo_pmp/test.rp2040.yaml b/tests/components/ezo_pmp/test.rp2040.yaml new file mode 100644 index 0000000000..fa047de3de --- /dev/null +++ b/tests/components/ezo_pmp/test.rp2040.yaml @@ -0,0 +1,61 @@ +i2c: + - id: i2c_ezo_pmp + scl: 5 + sda: 4 + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +binary_sensor: + - platform: ezo_pmp + pump_state: + name: Pump State + is_paused: + name: Is Paused + +sensor: + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? diff --git a/tests/components/factory_reset/test.esp32-c3-idf.yaml b/tests/components/factory_reset/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ad3abd603e --- /dev/null +++ b/tests/components/factory_reset/test.esp32-c3-idf.yaml @@ -0,0 +1,3 @@ +button: + - platform: factory_reset + name: Reset to Factory Default Settings diff --git a/tests/components/factory_reset/test.esp32-c3.yaml b/tests/components/factory_reset/test.esp32-c3.yaml new file mode 100644 index 0000000000..ad3abd603e --- /dev/null +++ b/tests/components/factory_reset/test.esp32-c3.yaml @@ -0,0 +1,3 @@ +button: + - platform: factory_reset + name: Reset to Factory Default Settings diff --git a/tests/components/factory_reset/test.esp32-idf.yaml b/tests/components/factory_reset/test.esp32-idf.yaml new file mode 100644 index 0000000000..ad3abd603e --- /dev/null +++ b/tests/components/factory_reset/test.esp32-idf.yaml @@ -0,0 +1,3 @@ +button: + - platform: factory_reset + name: Reset to Factory Default Settings diff --git a/tests/components/factory_reset/test.esp32.yaml b/tests/components/factory_reset/test.esp32.yaml new file mode 100644 index 0000000000..ad3abd603e --- /dev/null +++ b/tests/components/factory_reset/test.esp32.yaml @@ -0,0 +1,3 @@ +button: + - platform: factory_reset + name: Reset to Factory Default Settings diff --git a/tests/components/factory_reset/test.esp8266.yaml b/tests/components/factory_reset/test.esp8266.yaml new file mode 100644 index 0000000000..ad3abd603e --- /dev/null +++ b/tests/components/factory_reset/test.esp8266.yaml @@ -0,0 +1,3 @@ +button: + - platform: factory_reset + name: Reset to Factory Default Settings diff --git a/tests/components/factory_reset/test.rp2040.yaml b/tests/components/factory_reset/test.rp2040.yaml new file mode 100644 index 0000000000..ad3abd603e --- /dev/null +++ b/tests/components/factory_reset/test.rp2040.yaml @@ -0,0 +1,3 @@ +button: + - platform: factory_reset + name: Reset to Factory Default Settings diff --git a/tests/components/fastled_clockless/test.esp32.yaml b/tests/components/fastled_clockless/test.esp32.yaml new file mode 100644 index 0000000000..8b1447a17a --- /dev/null +++ b/tests/components/fastled_clockless/test.esp32.yaml @@ -0,0 +1,71 @@ +light: + - platform: fastled_clockless + id: addr1 + chipset: WS2811 + pin: 13 + num_leds: 100 + rgb_order: BRG + max_refresh_rate: 20ms + color_correct: [75%, 100%, 50%] + name: FastLED WS2811 Light + effects: + - addressable_color_wipe: + - addressable_color_wipe: + name: Color Wipe Effect With Custom Values + colors: + - red: 100% + green: 100% + blue: 100% + num_leds: 1 + - red: 0% + green: 0% + blue: 0% + num_leds: 1 + add_led_interval: 100ms + reverse: false + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - addressable_lambda: + name: Test For Custom Lambda Effect + lambda: |- + if (initial_run) { + it[0] = current_color; + } + - automation: + name: Custom Effect + sequence: + - light.addressable_set: + id: addr1 + red: 100% + green: 100% + blue: 0% + - delay: 100ms + - light.addressable_set: + id: addr1 + red: 0% + green: 100% + blue: 0% diff --git a/tests/components/fastled_spi/test.esp32.yaml b/tests/components/fastled_spi/test.esp32.yaml new file mode 100644 index 0000000000..f6f7c5553b --- /dev/null +++ b/tests/components/fastled_spi/test.esp32.yaml @@ -0,0 +1,71 @@ +light: + - platform: fastled_spi + id: addr1 + chipset: WS2801 + clock_pin: 22 + data_pin: 23 + data_rate: 2MHz + num_leds: 60 + rgb_order: BRG + name: FastLED SPI Light + effects: + - addressable_color_wipe: + - addressable_color_wipe: + name: Color Wipe Effect With Custom Values + colors: + - red: 100% + green: 100% + blue: 100% + num_leds: 1 + - red: 0% + green: 0% + blue: 0% + num_leds: 1 + add_led_interval: 100ms + reverse: false + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - addressable_lambda: + name: Test For Custom Lambda Effect + lambda: |- + if (initial_run) { + it[0] = current_color; + } + - automation: + name: Custom Effect + sequence: + - light.addressable_set: + id: addr1 + red: 100% + green: 100% + blue: 0% + - delay: 100ms + - light.addressable_set: + id: addr1 + red: 0% + green: 100% + blue: 0% diff --git a/tests/components/feedback/test.esp32-c3-idf.yaml b/tests/components/feedback/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..f93d54e8b6 --- /dev/null +++ b/tests/components/feedback/test.esp32-c3-idf.yaml @@ -0,0 +1,39 @@ +binary_sensor: + - platform: template + id: open_endstop_sensor + - platform: template + id: open_sensor + - platform: template + id: open_obstacle_sensor + - platform: template + id: close_endstop_sensor + - platform: template + id: close_sensor + - platform: template + id: close_obstacle_sensor + +cover: + - platform: feedback + name: Feedback Cover + id: gate + device_class: gate + infer_endstop_from_movement: false + has_built_in_endstop: false + max_duration: 30s + direction_change_wait_time: 300ms + acceleration_wait_time: 150ms + obstacle_rollback: 10% + open_duration: 22.1s + open_endstop: open_endstop_sensor + open_sensor: open_sensor + open_obstacle_sensor: open_obstacle_sensor + close_duration: 22.4s + close_endstop: close_endstop_sensor + close_sensor: close_sensor + close_obstacle_sensor: close_obstacle_sensor + open_action: + - logger.log: Open Action + close_action: + - logger.log: Close Action + stop_action: + - logger.log: Stop Action diff --git a/tests/components/feedback/test.esp32-c3.yaml b/tests/components/feedback/test.esp32-c3.yaml new file mode 100644 index 0000000000..f93d54e8b6 --- /dev/null +++ b/tests/components/feedback/test.esp32-c3.yaml @@ -0,0 +1,39 @@ +binary_sensor: + - platform: template + id: open_endstop_sensor + - platform: template + id: open_sensor + - platform: template + id: open_obstacle_sensor + - platform: template + id: close_endstop_sensor + - platform: template + id: close_sensor + - platform: template + id: close_obstacle_sensor + +cover: + - platform: feedback + name: Feedback Cover + id: gate + device_class: gate + infer_endstop_from_movement: false + has_built_in_endstop: false + max_duration: 30s + direction_change_wait_time: 300ms + acceleration_wait_time: 150ms + obstacle_rollback: 10% + open_duration: 22.1s + open_endstop: open_endstop_sensor + open_sensor: open_sensor + open_obstacle_sensor: open_obstacle_sensor + close_duration: 22.4s + close_endstop: close_endstop_sensor + close_sensor: close_sensor + close_obstacle_sensor: close_obstacle_sensor + open_action: + - logger.log: Open Action + close_action: + - logger.log: Close Action + stop_action: + - logger.log: Stop Action diff --git a/tests/components/feedback/test.esp32-idf.yaml b/tests/components/feedback/test.esp32-idf.yaml new file mode 100644 index 0000000000..f93d54e8b6 --- /dev/null +++ b/tests/components/feedback/test.esp32-idf.yaml @@ -0,0 +1,39 @@ +binary_sensor: + - platform: template + id: open_endstop_sensor + - platform: template + id: open_sensor + - platform: template + id: open_obstacle_sensor + - platform: template + id: close_endstop_sensor + - platform: template + id: close_sensor + - platform: template + id: close_obstacle_sensor + +cover: + - platform: feedback + name: Feedback Cover + id: gate + device_class: gate + infer_endstop_from_movement: false + has_built_in_endstop: false + max_duration: 30s + direction_change_wait_time: 300ms + acceleration_wait_time: 150ms + obstacle_rollback: 10% + open_duration: 22.1s + open_endstop: open_endstop_sensor + open_sensor: open_sensor + open_obstacle_sensor: open_obstacle_sensor + close_duration: 22.4s + close_endstop: close_endstop_sensor + close_sensor: close_sensor + close_obstacle_sensor: close_obstacle_sensor + open_action: + - logger.log: Open Action + close_action: + - logger.log: Close Action + stop_action: + - logger.log: Stop Action diff --git a/tests/components/feedback/test.esp32.yaml b/tests/components/feedback/test.esp32.yaml new file mode 100644 index 0000000000..f93d54e8b6 --- /dev/null +++ b/tests/components/feedback/test.esp32.yaml @@ -0,0 +1,39 @@ +binary_sensor: + - platform: template + id: open_endstop_sensor + - platform: template + id: open_sensor + - platform: template + id: open_obstacle_sensor + - platform: template + id: close_endstop_sensor + - platform: template + id: close_sensor + - platform: template + id: close_obstacle_sensor + +cover: + - platform: feedback + name: Feedback Cover + id: gate + device_class: gate + infer_endstop_from_movement: false + has_built_in_endstop: false + max_duration: 30s + direction_change_wait_time: 300ms + acceleration_wait_time: 150ms + obstacle_rollback: 10% + open_duration: 22.1s + open_endstop: open_endstop_sensor + open_sensor: open_sensor + open_obstacle_sensor: open_obstacle_sensor + close_duration: 22.4s + close_endstop: close_endstop_sensor + close_sensor: close_sensor + close_obstacle_sensor: close_obstacle_sensor + open_action: + - logger.log: Open Action + close_action: + - logger.log: Close Action + stop_action: + - logger.log: Stop Action diff --git a/tests/components/feedback/test.esp8266.yaml b/tests/components/feedback/test.esp8266.yaml new file mode 100644 index 0000000000..f93d54e8b6 --- /dev/null +++ b/tests/components/feedback/test.esp8266.yaml @@ -0,0 +1,39 @@ +binary_sensor: + - platform: template + id: open_endstop_sensor + - platform: template + id: open_sensor + - platform: template + id: open_obstacle_sensor + - platform: template + id: close_endstop_sensor + - platform: template + id: close_sensor + - platform: template + id: close_obstacle_sensor + +cover: + - platform: feedback + name: Feedback Cover + id: gate + device_class: gate + infer_endstop_from_movement: false + has_built_in_endstop: false + max_duration: 30s + direction_change_wait_time: 300ms + acceleration_wait_time: 150ms + obstacle_rollback: 10% + open_duration: 22.1s + open_endstop: open_endstop_sensor + open_sensor: open_sensor + open_obstacle_sensor: open_obstacle_sensor + close_duration: 22.4s + close_endstop: close_endstop_sensor + close_sensor: close_sensor + close_obstacle_sensor: close_obstacle_sensor + open_action: + - logger.log: Open Action + close_action: + - logger.log: Close Action + stop_action: + - logger.log: Stop Action diff --git a/tests/components/feedback/test.rp2040.yaml b/tests/components/feedback/test.rp2040.yaml new file mode 100644 index 0000000000..f93d54e8b6 --- /dev/null +++ b/tests/components/feedback/test.rp2040.yaml @@ -0,0 +1,39 @@ +binary_sensor: + - platform: template + id: open_endstop_sensor + - platform: template + id: open_sensor + - platform: template + id: open_obstacle_sensor + - platform: template + id: close_endstop_sensor + - platform: template + id: close_sensor + - platform: template + id: close_obstacle_sensor + +cover: + - platform: feedback + name: Feedback Cover + id: gate + device_class: gate + infer_endstop_from_movement: false + has_built_in_endstop: false + max_duration: 30s + direction_change_wait_time: 300ms + acceleration_wait_time: 150ms + obstacle_rollback: 10% + open_duration: 22.1s + open_endstop: open_endstop_sensor + open_sensor: open_sensor + open_obstacle_sensor: open_obstacle_sensor + close_duration: 22.4s + close_endstop: close_endstop_sensor + close_sensor: close_sensor + close_obstacle_sensor: close_obstacle_sensor + open_action: + - logger.log: Open Action + close_action: + - logger.log: Close Action + stop_action: + - logger.log: Stop Action diff --git a/tests/components/fingerprint_grow/test.esp32-c3-idf.yaml b/tests/components/fingerprint_grow/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..e7ac08eb28 --- /dev/null +++ b/tests/components/fingerprint_grow/test.esp32-c3-idf.yaml @@ -0,0 +1,56 @@ +esphome: + on_boot: + then: + - fingerprint_grow.enroll: + finger_id: 2 + num_scans: 2 + - fingerprint_grow.cancel_enroll: + - fingerprint_grow.delete: + finger_id: 2 + - fingerprint_grow.delete_all: + +uart: + - id: uart_fingerprint_grow + tx_pin: 4 + rx_pin: 5 + baud_rate: 57600 + +fingerprint_grow: + sensing_pin: 6 + password: 0x12FE37DC + new_password: 0xA65B9840 + on_finger_scan_start: + - logger.log: test_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - logger.log: test_fingerprint_grow_finger_scan_invalid + on_finger_scan_matched: + - logger.log: test_fingerprint_grow_finger_scan_matched + on_finger_scan_unmatched: + - logger.log: test_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - logger.log: test_fingerprint_grow_finger_scan_misplaced + on_enrollment_scan: + - logger.log: test_fingerprint_grow_enrollment_scan + on_enrollment_done: + - logger.log: test_fingerprint_grow_node_enrollment_done + on_enrollment_failed: + - logger.log: test_fingerprint_grow_enrollment_failed + +binary_sensor: + - platform: fingerprint_grow + name: Fingerprint Enrolling + +sensor: + - platform: fingerprint_grow + fingerprint_count: + name: Fingerprint Count + status: + name: Fingerprint Status + capacity: + name: Fingerprint Capacity + security_level: + name: Fingerprint Security Level + last_finger_id: + name: Fingerprint Last Finger ID + last_confidence: + name: Fingerprint Last Confidence diff --git a/tests/components/fingerprint_grow/test.esp32-c3.yaml b/tests/components/fingerprint_grow/test.esp32-c3.yaml new file mode 100644 index 0000000000..e7ac08eb28 --- /dev/null +++ b/tests/components/fingerprint_grow/test.esp32-c3.yaml @@ -0,0 +1,56 @@ +esphome: + on_boot: + then: + - fingerprint_grow.enroll: + finger_id: 2 + num_scans: 2 + - fingerprint_grow.cancel_enroll: + - fingerprint_grow.delete: + finger_id: 2 + - fingerprint_grow.delete_all: + +uart: + - id: uart_fingerprint_grow + tx_pin: 4 + rx_pin: 5 + baud_rate: 57600 + +fingerprint_grow: + sensing_pin: 6 + password: 0x12FE37DC + new_password: 0xA65B9840 + on_finger_scan_start: + - logger.log: test_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - logger.log: test_fingerprint_grow_finger_scan_invalid + on_finger_scan_matched: + - logger.log: test_fingerprint_grow_finger_scan_matched + on_finger_scan_unmatched: + - logger.log: test_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - logger.log: test_fingerprint_grow_finger_scan_misplaced + on_enrollment_scan: + - logger.log: test_fingerprint_grow_enrollment_scan + on_enrollment_done: + - logger.log: test_fingerprint_grow_node_enrollment_done + on_enrollment_failed: + - logger.log: test_fingerprint_grow_enrollment_failed + +binary_sensor: + - platform: fingerprint_grow + name: Fingerprint Enrolling + +sensor: + - platform: fingerprint_grow + fingerprint_count: + name: Fingerprint Count + status: + name: Fingerprint Status + capacity: + name: Fingerprint Capacity + security_level: + name: Fingerprint Security Level + last_finger_id: + name: Fingerprint Last Finger ID + last_confidence: + name: Fingerprint Last Confidence diff --git a/tests/components/fingerprint_grow/test.esp32-idf.yaml b/tests/components/fingerprint_grow/test.esp32-idf.yaml new file mode 100644 index 0000000000..0950145a05 --- /dev/null +++ b/tests/components/fingerprint_grow/test.esp32-idf.yaml @@ -0,0 +1,56 @@ +esphome: + on_boot: + then: + - fingerprint_grow.enroll: + finger_id: 2 + num_scans: 2 + - fingerprint_grow.cancel_enroll: + - fingerprint_grow.delete: + finger_id: 2 + - fingerprint_grow.delete_all: + +uart: + - id: uart_fingerprint_grow + tx_pin: 17 + rx_pin: 16 + baud_rate: 57600 + +fingerprint_grow: + sensing_pin: 18 + password: 0x12FE37DC + new_password: 0xA65B9840 + on_finger_scan_start: + - logger.log: test_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - logger.log: test_fingerprint_grow_finger_scan_invalid + on_finger_scan_matched: + - logger.log: test_fingerprint_grow_finger_scan_matched + on_finger_scan_unmatched: + - logger.log: test_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - logger.log: test_fingerprint_grow_finger_scan_misplaced + on_enrollment_scan: + - logger.log: test_fingerprint_grow_enrollment_scan + on_enrollment_done: + - logger.log: test_fingerprint_grow_node_enrollment_done + on_enrollment_failed: + - logger.log: test_fingerprint_grow_enrollment_failed + +binary_sensor: + - platform: fingerprint_grow + name: Fingerprint Enrolling + +sensor: + - platform: fingerprint_grow + fingerprint_count: + name: Fingerprint Count + status: + name: Fingerprint Status + capacity: + name: Fingerprint Capacity + security_level: + name: Fingerprint Security Level + last_finger_id: + name: Fingerprint Last Finger ID + last_confidence: + name: Fingerprint Last Confidence diff --git a/tests/components/fingerprint_grow/test.esp32.yaml b/tests/components/fingerprint_grow/test.esp32.yaml new file mode 100644 index 0000000000..0950145a05 --- /dev/null +++ b/tests/components/fingerprint_grow/test.esp32.yaml @@ -0,0 +1,56 @@ +esphome: + on_boot: + then: + - fingerprint_grow.enroll: + finger_id: 2 + num_scans: 2 + - fingerprint_grow.cancel_enroll: + - fingerprint_grow.delete: + finger_id: 2 + - fingerprint_grow.delete_all: + +uart: + - id: uart_fingerprint_grow + tx_pin: 17 + rx_pin: 16 + baud_rate: 57600 + +fingerprint_grow: + sensing_pin: 18 + password: 0x12FE37DC + new_password: 0xA65B9840 + on_finger_scan_start: + - logger.log: test_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - logger.log: test_fingerprint_grow_finger_scan_invalid + on_finger_scan_matched: + - logger.log: test_fingerprint_grow_finger_scan_matched + on_finger_scan_unmatched: + - logger.log: test_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - logger.log: test_fingerprint_grow_finger_scan_misplaced + on_enrollment_scan: + - logger.log: test_fingerprint_grow_enrollment_scan + on_enrollment_done: + - logger.log: test_fingerprint_grow_node_enrollment_done + on_enrollment_failed: + - logger.log: test_fingerprint_grow_enrollment_failed + +binary_sensor: + - platform: fingerprint_grow + name: Fingerprint Enrolling + +sensor: + - platform: fingerprint_grow + fingerprint_count: + name: Fingerprint Count + status: + name: Fingerprint Status + capacity: + name: Fingerprint Capacity + security_level: + name: Fingerprint Security Level + last_finger_id: + name: Fingerprint Last Finger ID + last_confidence: + name: Fingerprint Last Confidence diff --git a/tests/components/fingerprint_grow/test.esp8266.yaml b/tests/components/fingerprint_grow/test.esp8266.yaml new file mode 100644 index 0000000000..1d00d977b9 --- /dev/null +++ b/tests/components/fingerprint_grow/test.esp8266.yaml @@ -0,0 +1,56 @@ +esphome: + on_boot: + then: + - fingerprint_grow.enroll: + finger_id: 2 + num_scans: 2 + - fingerprint_grow.cancel_enroll: + - fingerprint_grow.delete: + finger_id: 2 + - fingerprint_grow.delete_all: + +uart: + - id: uart_fingerprint_grow + tx_pin: 4 + rx_pin: 5 + baud_rate: 57600 + +fingerprint_grow: + sensing_pin: 16 + password: 0x12FE37DC + new_password: 0xA65B9840 + on_finger_scan_start: + - logger.log: test_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - logger.log: test_fingerprint_grow_finger_scan_invalid + on_finger_scan_matched: + - logger.log: test_fingerprint_grow_finger_scan_matched + on_finger_scan_unmatched: + - logger.log: test_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - logger.log: test_fingerprint_grow_finger_scan_misplaced + on_enrollment_scan: + - logger.log: test_fingerprint_grow_enrollment_scan + on_enrollment_done: + - logger.log: test_fingerprint_grow_node_enrollment_done + on_enrollment_failed: + - logger.log: test_fingerprint_grow_enrollment_failed + +binary_sensor: + - platform: fingerprint_grow + name: Fingerprint Enrolling + +sensor: + - platform: fingerprint_grow + fingerprint_count: + name: Fingerprint Count + status: + name: Fingerprint Status + capacity: + name: Fingerprint Capacity + security_level: + name: Fingerprint Security Level + last_finger_id: + name: Fingerprint Last Finger ID + last_confidence: + name: Fingerprint Last Confidence diff --git a/tests/components/fingerprint_grow/test.rp2040.yaml b/tests/components/fingerprint_grow/test.rp2040.yaml new file mode 100644 index 0000000000..e7ac08eb28 --- /dev/null +++ b/tests/components/fingerprint_grow/test.rp2040.yaml @@ -0,0 +1,56 @@ +esphome: + on_boot: + then: + - fingerprint_grow.enroll: + finger_id: 2 + num_scans: 2 + - fingerprint_grow.cancel_enroll: + - fingerprint_grow.delete: + finger_id: 2 + - fingerprint_grow.delete_all: + +uart: + - id: uart_fingerprint_grow + tx_pin: 4 + rx_pin: 5 + baud_rate: 57600 + +fingerprint_grow: + sensing_pin: 6 + password: 0x12FE37DC + new_password: 0xA65B9840 + on_finger_scan_start: + - logger.log: test_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - logger.log: test_fingerprint_grow_finger_scan_invalid + on_finger_scan_matched: + - logger.log: test_fingerprint_grow_finger_scan_matched + on_finger_scan_unmatched: + - logger.log: test_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - logger.log: test_fingerprint_grow_finger_scan_misplaced + on_enrollment_scan: + - logger.log: test_fingerprint_grow_enrollment_scan + on_enrollment_done: + - logger.log: test_fingerprint_grow_node_enrollment_done + on_enrollment_failed: + - logger.log: test_fingerprint_grow_enrollment_failed + +binary_sensor: + - platform: fingerprint_grow + name: Fingerprint Enrolling + +sensor: + - platform: fingerprint_grow + fingerprint_count: + name: Fingerprint Count + status: + name: Fingerprint Status + capacity: + name: Fingerprint Capacity + security_level: + name: Fingerprint Security Level + last_finger_id: + name: Fingerprint Last Finger ID + last_confidence: + name: Fingerprint Last Confidence diff --git a/tests/components/font/test.esp32-c3-idf.yaml b/tests/components/font/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..b63b4d025a --- /dev/null +++ b/tests/components/font/test.esp32-c3-idf.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_font + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 diff --git a/tests/components/font/test.esp32-c3.yaml b/tests/components/font/test.esp32-c3.yaml new file mode 100644 index 0000000000..b63b4d025a --- /dev/null +++ b/tests/components/font/test.esp32-c3.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_font + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 diff --git a/tests/components/font/test.esp32-idf.yaml b/tests/components/font/test.esp32-idf.yaml new file mode 100644 index 0000000000..dcf8fb49d5 --- /dev/null +++ b/tests/components/font/test.esp32-idf.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_font + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 diff --git a/tests/components/font/test.esp32.yaml b/tests/components/font/test.esp32.yaml new file mode 100644 index 0000000000..d142463893 --- /dev/null +++ b/tests/components/font/test.esp32.yaml @@ -0,0 +1,38 @@ +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + glyphs: "0123456789." + extras: + - file: "gfonts://Roboto" + glyphs: ["\u00C4", "\u00C5", "\U000000C7"] + - file: "gfonts://Roboto" + id: roboto_web + size: 20 + - file: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf" + id: monocraft + size: 20 + - file: + type: web + url: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf" + id: monocraft2 + size: 24 + +spi: + clk_pin: 14 + mosi_pin: 13 + +display: + - id: my_display + platform: ili9xxx + dimensions: 480x320 + model: ST7796 + cs_pin: 15 + dc_pin: 21 + reset_pin: 22 + transform: + swap_xy: true + mirror_x: true + mirror_y: true + auto_clear_enabled: false + diff --git a/tests/components/font/test.esp8266.yaml b/tests/components/font/test.esp8266.yaml new file mode 100644 index 0000000000..b63b4d025a --- /dev/null +++ b/tests/components/font/test.esp8266.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_font + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 diff --git a/tests/components/font/test.rp2040.yaml b/tests/components/font/test.rp2040.yaml new file mode 100644 index 0000000000..b63b4d025a --- /dev/null +++ b/tests/components/font/test.rp2040.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_font + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 diff --git a/tests/components/fs3000/test.esp32-c3-idf.yaml b/tests/components/fs3000/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..69de83b463 --- /dev/null +++ b/tests/components/fs3000/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_fs3000 + scl: 5 + sda: 4 + +sensor: + - platform: fs3000 + name: Air Velocity + model: 1005 + update_interval: 60s diff --git a/tests/components/fs3000/test.esp32-c3.yaml b/tests/components/fs3000/test.esp32-c3.yaml new file mode 100644 index 0000000000..69de83b463 --- /dev/null +++ b/tests/components/fs3000/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_fs3000 + scl: 5 + sda: 4 + +sensor: + - platform: fs3000 + name: Air Velocity + model: 1005 + update_interval: 60s diff --git a/tests/components/fs3000/test.esp32-idf.yaml b/tests/components/fs3000/test.esp32-idf.yaml new file mode 100644 index 0000000000..53b49cc9a2 --- /dev/null +++ b/tests/components/fs3000/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_fs3000 + scl: 16 + sda: 17 + +sensor: + - platform: fs3000 + name: Air Velocity + model: 1005 + update_interval: 60s diff --git a/tests/components/fs3000/test.esp32.yaml b/tests/components/fs3000/test.esp32.yaml new file mode 100644 index 0000000000..53b49cc9a2 --- /dev/null +++ b/tests/components/fs3000/test.esp32.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_fs3000 + scl: 16 + sda: 17 + +sensor: + - platform: fs3000 + name: Air Velocity + model: 1005 + update_interval: 60s diff --git a/tests/components/fs3000/test.esp8266.yaml b/tests/components/fs3000/test.esp8266.yaml new file mode 100644 index 0000000000..69de83b463 --- /dev/null +++ b/tests/components/fs3000/test.esp8266.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_fs3000 + scl: 5 + sda: 4 + +sensor: + - platform: fs3000 + name: Air Velocity + model: 1005 + update_interval: 60s diff --git a/tests/components/fs3000/test.rp2040.yaml b/tests/components/fs3000/test.rp2040.yaml new file mode 100644 index 0000000000..69de83b463 --- /dev/null +++ b/tests/components/fs3000/test.rp2040.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_fs3000 + scl: 5 + sda: 4 + +sensor: + - platform: fs3000 + name: Air Velocity + model: 1005 + update_interval: 60s diff --git a/tests/components/ft5x06/test.esp32-c3-idf.yaml b/tests/components/ft5x06/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4fa9532f53 --- /dev/null +++ b/tests/components/ft5x06/test.esp32-c3-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft5x06 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft5x06 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft5x06/test.esp32-c3.yaml b/tests/components/ft5x06/test.esp32-c3.yaml new file mode 100644 index 0000000000..4fa9532f53 --- /dev/null +++ b/tests/components/ft5x06/test.esp32-c3.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft5x06 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft5x06 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft5x06/test.esp32-idf.yaml b/tests/components/ft5x06/test.esp32-idf.yaml new file mode 100644 index 0000000000..648929896d --- /dev/null +++ b/tests/components/ft5x06/test.esp32-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft5x06 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 18 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft5x06 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft5x06/test.esp32.yaml b/tests/components/ft5x06/test.esp32.yaml new file mode 100644 index 0000000000..648929896d --- /dev/null +++ b/tests/components/ft5x06/test.esp32.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft5x06 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 18 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft5x06 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft5x06/test.esp8266.yaml b/tests/components/ft5x06/test.esp8266.yaml new file mode 100644 index 0000000000..4fa9532f53 --- /dev/null +++ b/tests/components/ft5x06/test.esp8266.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft5x06 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft5x06 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft5x06/test.rp2040.yaml b/tests/components/ft5x06/test.rp2040.yaml new file mode 100644 index 0000000000..4fa9532f53 --- /dev/null +++ b/tests/components/ft5x06/test.rp2040.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft5x06 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft5x06 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft63x6/test.esp32-c3-idf.yaml b/tests/components/ft63x6/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..19ca4cfc19 --- /dev/null +++ b/tests/components/ft63x6/test.esp32-c3-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft63x6 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft63x6 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft63x6/test.esp32-c3.yaml b/tests/components/ft63x6/test.esp32-c3.yaml new file mode 100644 index 0000000000..19ca4cfc19 --- /dev/null +++ b/tests/components/ft63x6/test.esp32-c3.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft63x6 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft63x6 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft63x6/test.esp32-idf.yaml b/tests/components/ft63x6/test.esp32-idf.yaml new file mode 100644 index 0000000000..5ceb107e31 --- /dev/null +++ b/tests/components/ft63x6/test.esp32-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft63x6 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 18 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft63x6 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft63x6/test.esp32.yaml b/tests/components/ft63x6/test.esp32.yaml new file mode 100644 index 0000000000..32d6634dae --- /dev/null +++ b/tests/components/ft63x6/test.esp32.yaml @@ -0,0 +1,38 @@ +spi: + clk_pin: 14 + mosi_pin: 13 + +i2c: + sda: GPIO18 + scl: GPIO19 + +display: + - id: my_display + platform: ili9xxx + dimensions: 480x320 + model: ST7796 + cs_pin: 15 + dc_pin: 21 + reset_pin: 22 + transform: + swap_xy: true + mirror_x: true + mirror_y: true + auto_clear_enabled: false + +touchscreen: + - platform: ft63x6 + interrupt_pin: GPIO39 + transform: + swap_xy: true + mirror_x: false + mirror_y: true + on_touch: + - logger.log: + format: tp touched + on_update: + - logger.log: + format: to updated + on_release: + - logger.log: + format: to released diff --git a/tests/components/ft63x6/test.esp8266.yaml b/tests/components/ft63x6/test.esp8266.yaml new file mode 100644 index 0000000000..19ca4cfc19 --- /dev/null +++ b/tests/components/ft63x6/test.esp8266.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft63x6 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft63x6 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft63x6/test.rp2040.yaml b/tests/components/ft63x6/test.rp2040.yaml new file mode 100644 index 0000000000..19ca4cfc19 --- /dev/null +++ b/tests/components/ft63x6/test.rp2040.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft63x6 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft63x6 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/fujitsu_general/test.esp32-c3-idf.yaml b/tests/components/fujitsu_general/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..b4146f2a18 --- /dev/null +++ b/tests/components/fujitsu_general/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: fujitsu_general + name: Fujitsu General Climate diff --git a/tests/components/fujitsu_general/test.esp32-c3.yaml b/tests/components/fujitsu_general/test.esp32-c3.yaml new file mode 100644 index 0000000000..b4146f2a18 --- /dev/null +++ b/tests/components/fujitsu_general/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: fujitsu_general + name: Fujitsu General Climate diff --git a/tests/components/fujitsu_general/test.esp32-idf.yaml b/tests/components/fujitsu_general/test.esp32-idf.yaml new file mode 100644 index 0000000000..b4146f2a18 --- /dev/null +++ b/tests/components/fujitsu_general/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: fujitsu_general + name: Fujitsu General Climate diff --git a/tests/components/fujitsu_general/test.esp32.yaml b/tests/components/fujitsu_general/test.esp32.yaml new file mode 100644 index 0000000000..b4146f2a18 --- /dev/null +++ b/tests/components/fujitsu_general/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: fujitsu_general + name: Fujitsu General Climate diff --git a/tests/components/fujitsu_general/test.esp8266.yaml b/tests/components/fujitsu_general/test.esp8266.yaml new file mode 100644 index 0000000000..2a05bdde6b --- /dev/null +++ b/tests/components/fujitsu_general/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: fujitsu_general + name: Fujitsu General Climate diff --git a/tests/components/gcja5/test.esp32-c3-idf.yaml b/tests/components/gcja5/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ec8765be52 --- /dev/null +++ b/tests/components/gcja5/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +uart: + - id: uart_gcja5 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +sensor: + - platform: gcja5 + pm_1_0: + name: "Particulate Matter <1.0µm Concentration" + pm_2_5: + name: "Particulate Matter <2.5µm Concentration" + pm_10_0: + name: "Particulate Matter <10.0µm Concentration" + pmc_0_5: + name: "PMC 0.5" + pmc_1_0: + name: "PMC 1.0" + pmc_2_5: + name: "PMC 2.5" + pmc_5_0: + name: "PMC 5.0" + pmc_10_0: + name: "PMC 10.0" diff --git a/tests/components/gcja5/test.esp32-c3.yaml b/tests/components/gcja5/test.esp32-c3.yaml new file mode 100644 index 0000000000..ec8765be52 --- /dev/null +++ b/tests/components/gcja5/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +uart: + - id: uart_gcja5 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +sensor: + - platform: gcja5 + pm_1_0: + name: "Particulate Matter <1.0µm Concentration" + pm_2_5: + name: "Particulate Matter <2.5µm Concentration" + pm_10_0: + name: "Particulate Matter <10.0µm Concentration" + pmc_0_5: + name: "PMC 0.5" + pmc_1_0: + name: "PMC 1.0" + pmc_2_5: + name: "PMC 2.5" + pmc_5_0: + name: "PMC 5.0" + pmc_10_0: + name: "PMC 10.0" diff --git a/tests/components/gcja5/test.esp32-idf.yaml b/tests/components/gcja5/test.esp32-idf.yaml new file mode 100644 index 0000000000..bc0f89eb9e --- /dev/null +++ b/tests/components/gcja5/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +uart: + - id: uart_gcja5 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + parity: EVEN + +sensor: + - platform: gcja5 + pm_1_0: + name: "Particulate Matter <1.0µm Concentration" + pm_2_5: + name: "Particulate Matter <2.5µm Concentration" + pm_10_0: + name: "Particulate Matter <10.0µm Concentration" + pmc_0_5: + name: "PMC 0.5" + pmc_1_0: + name: "PMC 1.0" + pmc_2_5: + name: "PMC 2.5" + pmc_5_0: + name: "PMC 5.0" + pmc_10_0: + name: "PMC 10.0" diff --git a/tests/components/gcja5/test.esp32.yaml b/tests/components/gcja5/test.esp32.yaml new file mode 100644 index 0000000000..bc0f89eb9e --- /dev/null +++ b/tests/components/gcja5/test.esp32.yaml @@ -0,0 +1,25 @@ +uart: + - id: uart_gcja5 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + parity: EVEN + +sensor: + - platform: gcja5 + pm_1_0: + name: "Particulate Matter <1.0µm Concentration" + pm_2_5: + name: "Particulate Matter <2.5µm Concentration" + pm_10_0: + name: "Particulate Matter <10.0µm Concentration" + pmc_0_5: + name: "PMC 0.5" + pmc_1_0: + name: "PMC 1.0" + pmc_2_5: + name: "PMC 2.5" + pmc_5_0: + name: "PMC 5.0" + pmc_10_0: + name: "PMC 10.0" diff --git a/tests/components/gcja5/test.esp8266.yaml b/tests/components/gcja5/test.esp8266.yaml new file mode 100644 index 0000000000..ec8765be52 --- /dev/null +++ b/tests/components/gcja5/test.esp8266.yaml @@ -0,0 +1,25 @@ +uart: + - id: uart_gcja5 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +sensor: + - platform: gcja5 + pm_1_0: + name: "Particulate Matter <1.0µm Concentration" + pm_2_5: + name: "Particulate Matter <2.5µm Concentration" + pm_10_0: + name: "Particulate Matter <10.0µm Concentration" + pmc_0_5: + name: "PMC 0.5" + pmc_1_0: + name: "PMC 1.0" + pmc_2_5: + name: "PMC 2.5" + pmc_5_0: + name: "PMC 5.0" + pmc_10_0: + name: "PMC 10.0" diff --git a/tests/components/gcja5/test.rp2040.yaml b/tests/components/gcja5/test.rp2040.yaml new file mode 100644 index 0000000000..ec8765be52 --- /dev/null +++ b/tests/components/gcja5/test.rp2040.yaml @@ -0,0 +1,25 @@ +uart: + - id: uart_gcja5 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +sensor: + - platform: gcja5 + pm_1_0: + name: "Particulate Matter <1.0µm Concentration" + pm_2_5: + name: "Particulate Matter <2.5µm Concentration" + pm_10_0: + name: "Particulate Matter <10.0µm Concentration" + pmc_0_5: + name: "PMC 0.5" + pmc_1_0: + name: "PMC 1.0" + pmc_2_5: + name: "PMC 2.5" + pmc_5_0: + name: "PMC 5.0" + pmc_10_0: + name: "PMC 10.0" diff --git a/tests/components/globals/test.esp32-c3-idf.yaml b/tests/components/globals/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..224a91a270 --- /dev/null +++ b/tests/components/globals/test.esp32-c3-idf.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - globals.set: + id: glob_int + value: "10" + +globals: + - id: glob_int + type: int + restore_value: true + initial_value: "0" + - id: glob_float + type: float + restore_value: true + initial_value: "0.0f" + - id: glob_bool + type: bool + restore_value: false + initial_value: "true" + - id: glob_string + type: std::string + restore_value: false + # initial_value: "" + - id: glob_bool_processed + type: bool + restore_value: false + initial_value: "false" diff --git a/tests/components/globals/test.esp32-c3.yaml b/tests/components/globals/test.esp32-c3.yaml new file mode 100644 index 0000000000..224a91a270 --- /dev/null +++ b/tests/components/globals/test.esp32-c3.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - globals.set: + id: glob_int + value: "10" + +globals: + - id: glob_int + type: int + restore_value: true + initial_value: "0" + - id: glob_float + type: float + restore_value: true + initial_value: "0.0f" + - id: glob_bool + type: bool + restore_value: false + initial_value: "true" + - id: glob_string + type: std::string + restore_value: false + # initial_value: "" + - id: glob_bool_processed + type: bool + restore_value: false + initial_value: "false" diff --git a/tests/components/globals/test.esp32-idf.yaml b/tests/components/globals/test.esp32-idf.yaml new file mode 100644 index 0000000000..224a91a270 --- /dev/null +++ b/tests/components/globals/test.esp32-idf.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - globals.set: + id: glob_int + value: "10" + +globals: + - id: glob_int + type: int + restore_value: true + initial_value: "0" + - id: glob_float + type: float + restore_value: true + initial_value: "0.0f" + - id: glob_bool + type: bool + restore_value: false + initial_value: "true" + - id: glob_string + type: std::string + restore_value: false + # initial_value: "" + - id: glob_bool_processed + type: bool + restore_value: false + initial_value: "false" diff --git a/tests/components/globals/test.esp32.yaml b/tests/components/globals/test.esp32.yaml new file mode 100644 index 0000000000..224a91a270 --- /dev/null +++ b/tests/components/globals/test.esp32.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - globals.set: + id: glob_int + value: "10" + +globals: + - id: glob_int + type: int + restore_value: true + initial_value: "0" + - id: glob_float + type: float + restore_value: true + initial_value: "0.0f" + - id: glob_bool + type: bool + restore_value: false + initial_value: "true" + - id: glob_string + type: std::string + restore_value: false + # initial_value: "" + - id: glob_bool_processed + type: bool + restore_value: false + initial_value: "false" diff --git a/tests/components/globals/test.esp8266.yaml b/tests/components/globals/test.esp8266.yaml new file mode 100644 index 0000000000..224a91a270 --- /dev/null +++ b/tests/components/globals/test.esp8266.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - globals.set: + id: glob_int + value: "10" + +globals: + - id: glob_int + type: int + restore_value: true + initial_value: "0" + - id: glob_float + type: float + restore_value: true + initial_value: "0.0f" + - id: glob_bool + type: bool + restore_value: false + initial_value: "true" + - id: glob_string + type: std::string + restore_value: false + # initial_value: "" + - id: glob_bool_processed + type: bool + restore_value: false + initial_value: "false" diff --git a/tests/components/globals/test.rp2040.yaml b/tests/components/globals/test.rp2040.yaml new file mode 100644 index 0000000000..224a91a270 --- /dev/null +++ b/tests/components/globals/test.rp2040.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - globals.set: + id: glob_int + value: "10" + +globals: + - id: glob_int + type: int + restore_value: true + initial_value: "0" + - id: glob_float + type: float + restore_value: true + initial_value: "0.0f" + - id: glob_bool + type: bool + restore_value: false + initial_value: "true" + - id: glob_string + type: std::string + restore_value: false + # initial_value: "" + - id: glob_bool_processed + type: bool + restore_value: false + initial_value: "false" diff --git a/tests/components/gp8403/test.esp32-c3-idf.yaml b/tests/components/gp8403/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..fbc40b948b --- /dev/null +++ b/tests/components/gp8403/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_gp8403 + scl: 5 + sda: 4 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 diff --git a/tests/components/gp8403/test.esp32-c3.yaml b/tests/components/gp8403/test.esp32-c3.yaml new file mode 100644 index 0000000000..fbc40b948b --- /dev/null +++ b/tests/components/gp8403/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_gp8403 + scl: 5 + sda: 4 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 diff --git a/tests/components/gp8403/test.esp32-idf.yaml b/tests/components/gp8403/test.esp32-idf.yaml new file mode 100644 index 0000000000..8470a303e1 --- /dev/null +++ b/tests/components/gp8403/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_gp8403 + scl: 16 + sda: 17 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 diff --git a/tests/components/gp8403/test.esp32.yaml b/tests/components/gp8403/test.esp32.yaml new file mode 100644 index 0000000000..8470a303e1 --- /dev/null +++ b/tests/components/gp8403/test.esp32.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_gp8403 + scl: 16 + sda: 17 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 diff --git a/tests/components/gp8403/test.esp8266.yaml b/tests/components/gp8403/test.esp8266.yaml new file mode 100644 index 0000000000..fbc40b948b --- /dev/null +++ b/tests/components/gp8403/test.esp8266.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_gp8403 + scl: 5 + sda: 4 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 diff --git a/tests/components/gp8403/test.rp2040.yaml b/tests/components/gp8403/test.rp2040.yaml new file mode 100644 index 0000000000..fbc40b948b --- /dev/null +++ b/tests/components/gp8403/test.rp2040.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_gp8403 + scl: 5 + sda: 4 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 diff --git a/tests/components/gpio/test.esp32-c3-idf.yaml b/tests/components/gpio/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..3ca285117d --- /dev/null +++ b/tests/components/gpio/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: gpio + pin: 2 + id: gpio_binary_sensor + +output: + - platform: gpio + pin: 3 + id: gpio_output + +switch: + - platform: gpio + pin: 4 + id: gpio_switch diff --git a/tests/components/gpio/test.esp32-c3.yaml b/tests/components/gpio/test.esp32-c3.yaml new file mode 100644 index 0000000000..3ca285117d --- /dev/null +++ b/tests/components/gpio/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: gpio + pin: 2 + id: gpio_binary_sensor + +output: + - platform: gpio + pin: 3 + id: gpio_output + +switch: + - platform: gpio + pin: 4 + id: gpio_switch diff --git a/tests/components/gpio/test.esp32-idf.yaml b/tests/components/gpio/test.esp32-idf.yaml new file mode 100644 index 0000000000..30dfa94b68 --- /dev/null +++ b/tests/components/gpio/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: gpio + pin: 12 + id: gpio_binary_sensor + +output: + - platform: gpio + pin: 13 + id: gpio_output + +switch: + - platform: gpio + pin: 14 + id: gpio_switch diff --git a/tests/components/gpio/test.esp32.yaml b/tests/components/gpio/test.esp32.yaml new file mode 100644 index 0000000000..30dfa94b68 --- /dev/null +++ b/tests/components/gpio/test.esp32.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: gpio + pin: 12 + id: gpio_binary_sensor + +output: + - platform: gpio + pin: 13 + id: gpio_output + +switch: + - platform: gpio + pin: 14 + id: gpio_switch diff --git a/tests/components/gpio/test.esp8266.yaml b/tests/components/gpio/test.esp8266.yaml new file mode 100644 index 0000000000..30dfa94b68 --- /dev/null +++ b/tests/components/gpio/test.esp8266.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: gpio + pin: 12 + id: gpio_binary_sensor + +output: + - platform: gpio + pin: 13 + id: gpio_output + +switch: + - platform: gpio + pin: 14 + id: gpio_switch diff --git a/tests/components/gpio/test.rp2040.yaml b/tests/components/gpio/test.rp2040.yaml new file mode 100644 index 0000000000..3ca285117d --- /dev/null +++ b/tests/components/gpio/test.rp2040.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: gpio + pin: 2 + id: gpio_binary_sensor + +output: + - platform: gpio + pin: 3 + id: gpio_output + +switch: + - platform: gpio + pin: 4 + id: gpio_switch diff --git a/tests/components/gps/test.esp32-c3.yaml b/tests/components/gps/test.esp32-c3.yaml new file mode 100644 index 0000000000..031f45b873 --- /dev/null +++ b/tests/components/gps/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_gps + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +gps: + +time: + - platform: gps + on_time_sync: + then: + logger.log: "It's time!" diff --git a/tests/components/gps/test.esp32.yaml b/tests/components/gps/test.esp32.yaml new file mode 100644 index 0000000000..c4e4cf9f6f --- /dev/null +++ b/tests/components/gps/test.esp32.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_gps + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + parity: EVEN + +gps: + +time: + - platform: gps + on_time_sync: + then: + logger.log: "It's time!" diff --git a/tests/components/gps/test.esp8266.yaml b/tests/components/gps/test.esp8266.yaml new file mode 100644 index 0000000000..031f45b873 --- /dev/null +++ b/tests/components/gps/test.esp8266.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_gps + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +gps: + +time: + - platform: gps + on_time_sync: + then: + logger.log: "It's time!" diff --git a/tests/components/gps/test.rp2040.yaml b/tests/components/gps/test.rp2040.yaml new file mode 100644 index 0000000000..031f45b873 --- /dev/null +++ b/tests/components/gps/test.rp2040.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_gps + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +gps: + +time: + - platform: gps + on_time_sync: + then: + logger.log: "It's time!" diff --git a/tests/components/graph/test.esp32-c3-idf.yaml b/tests/components/graph/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..8ce40e84ac --- /dev/null +++ b/tests/components/graph/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_graph + scl: 5 + sda: 4 + +sensor: + - platform: template + id: some_sensor + +graph: + - id: some_graph + sensor: some_sensor + duration: 1h + width: 100 + height: 100 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/graph/test.esp32-c3.yaml b/tests/components/graph/test.esp32-c3.yaml new file mode 100644 index 0000000000..8ce40e84ac --- /dev/null +++ b/tests/components/graph/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_graph + scl: 5 + sda: 4 + +sensor: + - platform: template + id: some_sensor + +graph: + - id: some_graph + sensor: some_sensor + duration: 1h + width: 100 + height: 100 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/graph/test.esp32-idf.yaml b/tests/components/graph/test.esp32-idf.yaml new file mode 100644 index 0000000000..8c0c0d4c9e --- /dev/null +++ b/tests/components/graph/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_graph + scl: 16 + sda: 17 + +sensor: + - platform: template + id: some_sensor + +graph: + - id: some_graph + sensor: some_sensor + duration: 1h + width: 100 + height: 100 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/graph/test.esp32.yaml b/tests/components/graph/test.esp32.yaml new file mode 100644 index 0000000000..8c0c0d4c9e --- /dev/null +++ b/tests/components/graph/test.esp32.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_graph + scl: 16 + sda: 17 + +sensor: + - platform: template + id: some_sensor + +graph: + - id: some_graph + sensor: some_sensor + duration: 1h + width: 100 + height: 100 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/graph/test.esp8266.yaml b/tests/components/graph/test.esp8266.yaml new file mode 100644 index 0000000000..33318355d5 --- /dev/null +++ b/tests/components/graph/test.esp8266.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_graph + scl: 5 + sda: 4 + +sensor: + - platform: template + id: some_sensor + +graph: + - id: some_graph + sensor: some_sensor + duration: 1h + width: 100 + height: 100 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/graph/test.rp2040.yaml b/tests/components/graph/test.rp2040.yaml new file mode 100644 index 0000000000..8ce40e84ac --- /dev/null +++ b/tests/components/graph/test.rp2040.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_graph + scl: 5 + sda: 4 + +sensor: + - platform: template + id: some_sensor + +graph: + - id: some_graph + sensor: some_sensor + duration: 1h + width: 100 + height: 100 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/graphical_display_menu/test.esp32-c3-idf.yaml b/tests/components/graphical_display_menu/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..23acd4e4d9 --- /dev/null +++ b/tests/components/graphical_display_menu/test.esp32-c3-idf.yaml @@ -0,0 +1,120 @@ +i2c: + - id: i2c_graphical_display_menu + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + id: test_switch + optimistic: true + +graphical_display_menu: + id: test_graphical_display_menu + display: ssd1306_display + font: roboto + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' + items: + - type: back + text: "Back" + - type: label + - type: menu + text: "Submenu 1" + items: + - type: back + text: "Back" + - type: menu + text: "Submenu 21" + items: + - type: back + text: "Back" + - type: command + text: "Show Main" + on_value: + then: + - display_menu.show_main: test_graphical_display_menu + - type: select + text: "Enum Item" + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: "Number" + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: "Hide" + on_value: + then: + - display_menu.hide: test_graphical_display_menu + - type: switch + text: "Switch" + switch: test_switch + on_text: "Bright" + off_text: "Dark" + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/graphical_display_menu/test.esp32-c3.yaml b/tests/components/graphical_display_menu/test.esp32-c3.yaml new file mode 100644 index 0000000000..23acd4e4d9 --- /dev/null +++ b/tests/components/graphical_display_menu/test.esp32-c3.yaml @@ -0,0 +1,120 @@ +i2c: + - id: i2c_graphical_display_menu + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + id: test_switch + optimistic: true + +graphical_display_menu: + id: test_graphical_display_menu + display: ssd1306_display + font: roboto + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' + items: + - type: back + text: "Back" + - type: label + - type: menu + text: "Submenu 1" + items: + - type: back + text: "Back" + - type: menu + text: "Submenu 21" + items: + - type: back + text: "Back" + - type: command + text: "Show Main" + on_value: + then: + - display_menu.show_main: test_graphical_display_menu + - type: select + text: "Enum Item" + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: "Number" + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: "Hide" + on_value: + then: + - display_menu.hide: test_graphical_display_menu + - type: switch + text: "Switch" + switch: test_switch + on_text: "Bright" + off_text: "Dark" + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/graphical_display_menu/test.esp32-idf.yaml b/tests/components/graphical_display_menu/test.esp32-idf.yaml new file mode 100644 index 0000000000..a0897536d7 --- /dev/null +++ b/tests/components/graphical_display_menu/test.esp32-idf.yaml @@ -0,0 +1,120 @@ +i2c: + - id: i2c_graphical_display_menu + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + id: test_switch + optimistic: true + +graphical_display_menu: + id: test_graphical_display_menu + display: ssd1306_display + font: roboto + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' + items: + - type: back + text: "Back" + - type: label + - type: menu + text: "Submenu 1" + items: + - type: back + text: "Back" + - type: menu + text: "Submenu 21" + items: + - type: back + text: "Back" + - type: command + text: "Show Main" + on_value: + then: + - display_menu.show_main: test_graphical_display_menu + - type: select + text: "Enum Item" + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: "Number" + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: "Hide" + on_value: + then: + - display_menu.hide: test_graphical_display_menu + - type: switch + text: "Switch" + switch: test_switch + on_text: "Bright" + off_text: "Dark" + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/graphical_display_menu/test.esp32.yaml b/tests/components/graphical_display_menu/test.esp32.yaml new file mode 100644 index 0000000000..a0897536d7 --- /dev/null +++ b/tests/components/graphical_display_menu/test.esp32.yaml @@ -0,0 +1,120 @@ +i2c: + - id: i2c_graphical_display_menu + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + id: test_switch + optimistic: true + +graphical_display_menu: + id: test_graphical_display_menu + display: ssd1306_display + font: roboto + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' + items: + - type: back + text: "Back" + - type: label + - type: menu + text: "Submenu 1" + items: + - type: back + text: "Back" + - type: menu + text: "Submenu 21" + items: + - type: back + text: "Back" + - type: command + text: "Show Main" + on_value: + then: + - display_menu.show_main: test_graphical_display_menu + - type: select + text: "Enum Item" + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: "Number" + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: "Hide" + on_value: + then: + - display_menu.hide: test_graphical_display_menu + - type: switch + text: "Switch" + switch: test_switch + on_text: "Bright" + off_text: "Dark" + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/graphical_display_menu/test.esp8266.yaml b/tests/components/graphical_display_menu/test.esp8266.yaml new file mode 100644 index 0000000000..28c1a7298d --- /dev/null +++ b/tests/components/graphical_display_menu/test.esp8266.yaml @@ -0,0 +1,120 @@ +i2c: + - id: i2c_graphical_display_menu + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + id: test_switch + optimistic: true + +graphical_display_menu: + id: test_graphical_display_menu + display: ssd1306_display + font: roboto + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' + items: + - type: back + text: "Back" + - type: label + - type: menu + text: "Submenu 1" + items: + - type: back + text: "Back" + - type: menu + text: "Submenu 21" + items: + - type: back + text: "Back" + - type: command + text: "Show Main" + on_value: + then: + - display_menu.show_main: test_graphical_display_menu + - type: select + text: "Enum Item" + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: "Number" + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: "Hide" + on_value: + then: + - display_menu.hide: test_graphical_display_menu + - type: switch + text: "Switch" + switch: test_switch + on_text: "Bright" + off_text: "Dark" + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/graphical_display_menu/test.rp2040.yaml b/tests/components/graphical_display_menu/test.rp2040.yaml new file mode 100644 index 0000000000..23acd4e4d9 --- /dev/null +++ b/tests/components/graphical_display_menu/test.rp2040.yaml @@ -0,0 +1,120 @@ +i2c: + - id: i2c_graphical_display_menu + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + id: test_switch + optimistic: true + +graphical_display_menu: + id: test_graphical_display_menu + display: ssd1306_display + font: roboto + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' + items: + - type: back + text: "Back" + - type: label + - type: menu + text: "Submenu 1" + items: + - type: back + text: "Back" + - type: menu + text: "Submenu 21" + items: + - type: back + text: "Back" + - type: command + text: "Show Main" + on_value: + then: + - display_menu.show_main: test_graphical_display_menu + - type: select + text: "Enum Item" + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: "Number" + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: "Hide" + on_value: + then: + - display_menu.hide: test_graphical_display_menu + - type: switch + text: "Switch" + switch: test_switch + on_text: "Bright" + off_text: "Dark" + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/gree/test.esp32-c3-idf.yaml b/tests/components/gree/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..91491d7e16 --- /dev/null +++ b/tests/components/gree/test.esp32-c3-idf.yaml @@ -0,0 +1,8 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: gree + name: GREE + model: generic diff --git a/tests/components/gree/test.esp32-c3.yaml b/tests/components/gree/test.esp32-c3.yaml new file mode 100644 index 0000000000..91491d7e16 --- /dev/null +++ b/tests/components/gree/test.esp32-c3.yaml @@ -0,0 +1,8 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: gree + name: GREE + model: generic diff --git a/tests/components/gree/test.esp32-idf.yaml b/tests/components/gree/test.esp32-idf.yaml new file mode 100644 index 0000000000..91491d7e16 --- /dev/null +++ b/tests/components/gree/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: gree + name: GREE + model: generic diff --git a/tests/components/gree/test.esp32.yaml b/tests/components/gree/test.esp32.yaml new file mode 100644 index 0000000000..91491d7e16 --- /dev/null +++ b/tests/components/gree/test.esp32.yaml @@ -0,0 +1,8 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: gree + name: GREE + model: generic diff --git a/tests/components/gree/test.esp8266.yaml b/tests/components/gree/test.esp8266.yaml new file mode 100644 index 0000000000..d0542973ce --- /dev/null +++ b/tests/components/gree/test.esp8266.yaml @@ -0,0 +1,8 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: gree + name: GREE + model: generic diff --git a/tests/components/grove_tb6612fng/test.esp32-c3-idf.yaml b/tests/components/grove_tb6612fng/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ef6dff6539 --- /dev/null +++ b/tests/components/grove_tb6612fng/test.esp32-c3-idf.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - grove_tb6612fng.run: + channel: 1 + speed: 255 + direction: BACKWARD + id: test_motor + - grove_tb6612fng.stop: + channel: 1 + id: test_motor + - grove_tb6612fng.break: + channel: 1 + id: test_motor + +i2c: + - id: i2c_grove_tb6612fng + scl: 5 + sda: 4 + +grove_tb6612fng: + id: test_motor + address: 0x14 diff --git a/tests/components/grove_tb6612fng/test.esp32-c3.yaml b/tests/components/grove_tb6612fng/test.esp32-c3.yaml new file mode 100644 index 0000000000..ef6dff6539 --- /dev/null +++ b/tests/components/grove_tb6612fng/test.esp32-c3.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - grove_tb6612fng.run: + channel: 1 + speed: 255 + direction: BACKWARD + id: test_motor + - grove_tb6612fng.stop: + channel: 1 + id: test_motor + - grove_tb6612fng.break: + channel: 1 + id: test_motor + +i2c: + - id: i2c_grove_tb6612fng + scl: 5 + sda: 4 + +grove_tb6612fng: + id: test_motor + address: 0x14 diff --git a/tests/components/grove_tb6612fng/test.esp32-idf.yaml b/tests/components/grove_tb6612fng/test.esp32-idf.yaml new file mode 100644 index 0000000000..3271fb754f --- /dev/null +++ b/tests/components/grove_tb6612fng/test.esp32-idf.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - grove_tb6612fng.run: + channel: 1 + speed: 255 + direction: BACKWARD + id: test_motor + - grove_tb6612fng.stop: + channel: 1 + id: test_motor + - grove_tb6612fng.break: + channel: 1 + id: test_motor + +i2c: + - id: i2c_grove_tb6612fng + scl: 16 + sda: 17 + +grove_tb6612fng: + id: test_motor + address: 0x14 diff --git a/tests/components/grove_tb6612fng/test.esp32.yaml b/tests/components/grove_tb6612fng/test.esp32.yaml new file mode 100644 index 0000000000..3271fb754f --- /dev/null +++ b/tests/components/grove_tb6612fng/test.esp32.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - grove_tb6612fng.run: + channel: 1 + speed: 255 + direction: BACKWARD + id: test_motor + - grove_tb6612fng.stop: + channel: 1 + id: test_motor + - grove_tb6612fng.break: + channel: 1 + id: test_motor + +i2c: + - id: i2c_grove_tb6612fng + scl: 16 + sda: 17 + +grove_tb6612fng: + id: test_motor + address: 0x14 diff --git a/tests/components/grove_tb6612fng/test.esp8266.yaml b/tests/components/grove_tb6612fng/test.esp8266.yaml new file mode 100644 index 0000000000..ef6dff6539 --- /dev/null +++ b/tests/components/grove_tb6612fng/test.esp8266.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - grove_tb6612fng.run: + channel: 1 + speed: 255 + direction: BACKWARD + id: test_motor + - grove_tb6612fng.stop: + channel: 1 + id: test_motor + - grove_tb6612fng.break: + channel: 1 + id: test_motor + +i2c: + - id: i2c_grove_tb6612fng + scl: 5 + sda: 4 + +grove_tb6612fng: + id: test_motor + address: 0x14 diff --git a/tests/components/grove_tb6612fng/test.rp2040.yaml b/tests/components/grove_tb6612fng/test.rp2040.yaml new file mode 100644 index 0000000000..ef6dff6539 --- /dev/null +++ b/tests/components/grove_tb6612fng/test.rp2040.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - grove_tb6612fng.run: + channel: 1 + speed: 255 + direction: BACKWARD + id: test_motor + - grove_tb6612fng.stop: + channel: 1 + id: test_motor + - grove_tb6612fng.break: + channel: 1 + id: test_motor + +i2c: + - id: i2c_grove_tb6612fng + scl: 5 + sda: 4 + +grove_tb6612fng: + id: test_motor + address: 0x14 diff --git a/tests/components/growatt_solar/test.esp32-c3-idf.yaml b/tests/components/growatt_solar/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..7e73897856 --- /dev/null +++ b/tests/components/growatt_solar/test.esp32-c3-idf.yaml @@ -0,0 +1,62 @@ +uart: + - id: uart_growatt_solar + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: growatt_solar + update_interval: 10s + protocol_version: RTU + inverter_status: + name: Growatt Status Code + phase_a: + voltage: + name: Growatt Voltage Phase A + current: + name: Growatt Current Phase A + active_power: + name: Growatt Power Phase A + phase_b: + voltage: + name: Growatt Voltage Phase B + current: + name: Growatt Current Phase B + active_power: + name: Growatt Power Phase B + phase_c: + voltage: + name: Growatt Voltage Phase C + current: + name: Growatt Current Phase C + active_power: + name: Growatt Power Phase C + pv1: + voltage: + name: Growatt PV1 Voltage + current: + name: Growatt PV1 Current + active_power: + name: Growatt PV1 Active Power + pv2: + voltage: + name: Growatt PV2 Voltage + current: + name: Growatt PV2 Current + active_power: + name: Growatt PV2 Active Power + active_power: + name: Growatt Grid Active Power + pv_active_power: + name: Growatt PV Active Power + frequency: + name: Growatt Frequency + energy_production_day: + name: Growatt Today's Generation + total_energy_production: + name: Growatt Total Energy Production + inverter_module_temp: + name: Growatt Inverter Module Temp diff --git a/tests/components/growatt_solar/test.esp32-c3.yaml b/tests/components/growatt_solar/test.esp32-c3.yaml new file mode 100644 index 0000000000..7e73897856 --- /dev/null +++ b/tests/components/growatt_solar/test.esp32-c3.yaml @@ -0,0 +1,62 @@ +uart: + - id: uart_growatt_solar + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: growatt_solar + update_interval: 10s + protocol_version: RTU + inverter_status: + name: Growatt Status Code + phase_a: + voltage: + name: Growatt Voltage Phase A + current: + name: Growatt Current Phase A + active_power: + name: Growatt Power Phase A + phase_b: + voltage: + name: Growatt Voltage Phase B + current: + name: Growatt Current Phase B + active_power: + name: Growatt Power Phase B + phase_c: + voltage: + name: Growatt Voltage Phase C + current: + name: Growatt Current Phase C + active_power: + name: Growatt Power Phase C + pv1: + voltage: + name: Growatt PV1 Voltage + current: + name: Growatt PV1 Current + active_power: + name: Growatt PV1 Active Power + pv2: + voltage: + name: Growatt PV2 Voltage + current: + name: Growatt PV2 Current + active_power: + name: Growatt PV2 Active Power + active_power: + name: Growatt Grid Active Power + pv_active_power: + name: Growatt PV Active Power + frequency: + name: Growatt Frequency + energy_production_day: + name: Growatt Today's Generation + total_energy_production: + name: Growatt Total Energy Production + inverter_module_temp: + name: Growatt Inverter Module Temp diff --git a/tests/components/growatt_solar/test.esp32-idf.yaml b/tests/components/growatt_solar/test.esp32-idf.yaml new file mode 100644 index 0000000000..654f2ccedf --- /dev/null +++ b/tests/components/growatt_solar/test.esp32-idf.yaml @@ -0,0 +1,62 @@ +uart: + - id: uart_growatt_solar + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + flow_control_pin: 13 + +sensor: + - platform: growatt_solar + update_interval: 10s + protocol_version: RTU + inverter_status: + name: Growatt Status Code + phase_a: + voltage: + name: Growatt Voltage Phase A + current: + name: Growatt Current Phase A + active_power: + name: Growatt Power Phase A + phase_b: + voltage: + name: Growatt Voltage Phase B + current: + name: Growatt Current Phase B + active_power: + name: Growatt Power Phase B + phase_c: + voltage: + name: Growatt Voltage Phase C + current: + name: Growatt Current Phase C + active_power: + name: Growatt Power Phase C + pv1: + voltage: + name: Growatt PV1 Voltage + current: + name: Growatt PV1 Current + active_power: + name: Growatt PV1 Active Power + pv2: + voltage: + name: Growatt PV2 Voltage + current: + name: Growatt PV2 Current + active_power: + name: Growatt PV2 Active Power + active_power: + name: Growatt Grid Active Power + pv_active_power: + name: Growatt PV Active Power + frequency: + name: Growatt Frequency + energy_production_day: + name: Growatt Today's Generation + total_energy_production: + name: Growatt Total Energy Production + inverter_module_temp: + name: Growatt Inverter Module Temp diff --git a/tests/components/growatt_solar/test.esp32.yaml b/tests/components/growatt_solar/test.esp32.yaml new file mode 100644 index 0000000000..654f2ccedf --- /dev/null +++ b/tests/components/growatt_solar/test.esp32.yaml @@ -0,0 +1,62 @@ +uart: + - id: uart_growatt_solar + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + flow_control_pin: 13 + +sensor: + - platform: growatt_solar + update_interval: 10s + protocol_version: RTU + inverter_status: + name: Growatt Status Code + phase_a: + voltage: + name: Growatt Voltage Phase A + current: + name: Growatt Current Phase A + active_power: + name: Growatt Power Phase A + phase_b: + voltage: + name: Growatt Voltage Phase B + current: + name: Growatt Current Phase B + active_power: + name: Growatt Power Phase B + phase_c: + voltage: + name: Growatt Voltage Phase C + current: + name: Growatt Current Phase C + active_power: + name: Growatt Power Phase C + pv1: + voltage: + name: Growatt PV1 Voltage + current: + name: Growatt PV1 Current + active_power: + name: Growatt PV1 Active Power + pv2: + voltage: + name: Growatt PV2 Voltage + current: + name: Growatt PV2 Current + active_power: + name: Growatt PV2 Active Power + active_power: + name: Growatt Grid Active Power + pv_active_power: + name: Growatt PV Active Power + frequency: + name: Growatt Frequency + energy_production_day: + name: Growatt Today's Generation + total_energy_production: + name: Growatt Total Energy Production + inverter_module_temp: + name: Growatt Inverter Module Temp diff --git a/tests/components/growatt_solar/test.esp8266.yaml b/tests/components/growatt_solar/test.esp8266.yaml new file mode 100644 index 0000000000..a1cf8267ae --- /dev/null +++ b/tests/components/growatt_solar/test.esp8266.yaml @@ -0,0 +1,62 @@ +uart: + - id: uart_growatt_solar + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 13 + +sensor: + - platform: growatt_solar + update_interval: 10s + protocol_version: RTU + inverter_status: + name: Growatt Status Code + phase_a: + voltage: + name: Growatt Voltage Phase A + current: + name: Growatt Current Phase A + active_power: + name: Growatt Power Phase A + phase_b: + voltage: + name: Growatt Voltage Phase B + current: + name: Growatt Current Phase B + active_power: + name: Growatt Power Phase B + phase_c: + voltage: + name: Growatt Voltage Phase C + current: + name: Growatt Current Phase C + active_power: + name: Growatt Power Phase C + pv1: + voltage: + name: Growatt PV1 Voltage + current: + name: Growatt PV1 Current + active_power: + name: Growatt PV1 Active Power + pv2: + voltage: + name: Growatt PV2 Voltage + current: + name: Growatt PV2 Current + active_power: + name: Growatt PV2 Active Power + active_power: + name: Growatt Grid Active Power + pv_active_power: + name: Growatt PV Active Power + frequency: + name: Growatt Frequency + energy_production_day: + name: Growatt Today's Generation + total_energy_production: + name: Growatt Total Energy Production + inverter_module_temp: + name: Growatt Inverter Module Temp diff --git a/tests/components/growatt_solar/test.rp2040.yaml b/tests/components/growatt_solar/test.rp2040.yaml new file mode 100644 index 0000000000..7e73897856 --- /dev/null +++ b/tests/components/growatt_solar/test.rp2040.yaml @@ -0,0 +1,62 @@ +uart: + - id: uart_growatt_solar + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: growatt_solar + update_interval: 10s + protocol_version: RTU + inverter_status: + name: Growatt Status Code + phase_a: + voltage: + name: Growatt Voltage Phase A + current: + name: Growatt Current Phase A + active_power: + name: Growatt Power Phase A + phase_b: + voltage: + name: Growatt Voltage Phase B + current: + name: Growatt Current Phase B + active_power: + name: Growatt Power Phase B + phase_c: + voltage: + name: Growatt Voltage Phase C + current: + name: Growatt Current Phase C + active_power: + name: Growatt Power Phase C + pv1: + voltage: + name: Growatt PV1 Voltage + current: + name: Growatt PV1 Current + active_power: + name: Growatt PV1 Active Power + pv2: + voltage: + name: Growatt PV2 Voltage + current: + name: Growatt PV2 Current + active_power: + name: Growatt PV2 Active Power + active_power: + name: Growatt Grid Active Power + pv_active_power: + name: Growatt PV Active Power + frequency: + name: Growatt Frequency + energy_production_day: + name: Growatt Today's Generation + total_energy_production: + name: Growatt Total Energy Production + inverter_module_temp: + name: Growatt Inverter Module Temp diff --git a/tests/components/gt911/test.esp32-c3-idf.yaml b/tests/components/gt911/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..43f7ac5902 --- /dev/null +++ b/tests/components/gt911/test.esp32-c3-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_gt911 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: gt911 + display: ssd1306_display + interrupt_pin: 6 + +binary_sensor: + - platform: gt911 + id: touch_key_911 + index: 0 diff --git a/tests/components/gt911/test.esp32-c3.yaml b/tests/components/gt911/test.esp32-c3.yaml new file mode 100644 index 0000000000..43f7ac5902 --- /dev/null +++ b/tests/components/gt911/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_gt911 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: gt911 + display: ssd1306_display + interrupt_pin: 6 + +binary_sensor: + - platform: gt911 + id: touch_key_911 + index: 0 diff --git a/tests/components/gt911/test.esp32-idf.yaml b/tests/components/gt911/test.esp32-idf.yaml new file mode 100644 index 0000000000..a47f7bf260 --- /dev/null +++ b/tests/components/gt911/test.esp32-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_gt911 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: gt911 + display: ssd1306_display + interrupt_pin: 14 + +binary_sensor: + - platform: gt911 + id: touch_key_911 + index: 0 diff --git a/tests/components/gt911/test.esp32.yaml b/tests/components/gt911/test.esp32.yaml new file mode 100644 index 0000000000..a47f7bf260 --- /dev/null +++ b/tests/components/gt911/test.esp32.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_gt911 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: gt911 + display: ssd1306_display + interrupt_pin: 14 + +binary_sensor: + - platform: gt911 + id: touch_key_911 + index: 0 diff --git a/tests/components/gt911/test.esp8266.yaml b/tests/components/gt911/test.esp8266.yaml new file mode 100644 index 0000000000..8b76eff29e --- /dev/null +++ b/tests/components/gt911/test.esp8266.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_gt911 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: gt911 + display: ssd1306_display + interrupt_pin: 12 + +binary_sensor: + - platform: gt911 + id: touch_key_911 + index: 0 diff --git a/tests/components/gt911/test.rp2040.yaml b/tests/components/gt911/test.rp2040.yaml new file mode 100644 index 0000000000..43f7ac5902 --- /dev/null +++ b/tests/components/gt911/test.rp2040.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_gt911 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: gt911 + display: ssd1306_display + interrupt_pin: 6 + +binary_sensor: + - platform: gt911 + id: touch_key_911 + index: 0 diff --git a/tests/components/haier/test.esp32-c3-idf.yaml b/tests/components/haier/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..72cfb781a7 --- /dev/null +++ b/tests/components/haier/test.esp32-c3-idf.yaml @@ -0,0 +1,95 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_haier + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +climate: + - platform: haier + id: haier_ac + protocol: hOn + name: Haier AC + wifi_signal: true + answer_timeout: 200ms + beeper: true + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 + supported_modes: + - 'OFF' + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: "Alarm activated. Code: %d. Message: \"%s\"" + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: "Alarm deactivated. Code: %d. Message: \"%s\"" + args: [code, message] + +sensor: + - platform: haier + haier_id: haier_ac + outdoor_temperature: + name: Haier outdoor temperature + humidity: + name: Haier Indoor Humidity + compressor_current: + name: Haier Compressor Current + compressor_frequency: + name: Haier Compressor Frequency + expansion_valve_open_degree: + name: Haier Expansion Valve Open Degree + indoor_coil_temperature: + name: Haier Indoor Coil Temperature + outdoor_coil_temperature: + name: Haier Outdoor Coil Temperature + outdoor_defrost_temperature: + name: Haier Outdoor Defrost Temperature + outdoor_in_air_temperature: + name: Haier Outdoor In Air Temperature + outdoor_out_air_temperature: + name: Haier Outdoor Out Air Temperature + power: + name: Haier Power + +binary_sensor: + - platform: haier + haier_id: haier_ac + compressor_status: + name: Haier Outdoor Compressor Status + defrost_status: + name: Haier Defrost Status + four_way_valve_status: + name: Haier Four Way Valve Status + indoor_electric_heating_status: + name: Haier Indoor Electric Heating Status + indoor_fan_status: + name: Haier Indoor Fan Status + outdoor_fan_status: + name: Haier Outdoor Fan Status diff --git a/tests/components/haier/test.esp32-c3.yaml b/tests/components/haier/test.esp32-c3.yaml new file mode 100644 index 0000000000..72cfb781a7 --- /dev/null +++ b/tests/components/haier/test.esp32-c3.yaml @@ -0,0 +1,95 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_haier + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +climate: + - platform: haier + id: haier_ac + protocol: hOn + name: Haier AC + wifi_signal: true + answer_timeout: 200ms + beeper: true + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 + supported_modes: + - 'OFF' + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: "Alarm activated. Code: %d. Message: \"%s\"" + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: "Alarm deactivated. Code: %d. Message: \"%s\"" + args: [code, message] + +sensor: + - platform: haier + haier_id: haier_ac + outdoor_temperature: + name: Haier outdoor temperature + humidity: + name: Haier Indoor Humidity + compressor_current: + name: Haier Compressor Current + compressor_frequency: + name: Haier Compressor Frequency + expansion_valve_open_degree: + name: Haier Expansion Valve Open Degree + indoor_coil_temperature: + name: Haier Indoor Coil Temperature + outdoor_coil_temperature: + name: Haier Outdoor Coil Temperature + outdoor_defrost_temperature: + name: Haier Outdoor Defrost Temperature + outdoor_in_air_temperature: + name: Haier Outdoor In Air Temperature + outdoor_out_air_temperature: + name: Haier Outdoor Out Air Temperature + power: + name: Haier Power + +binary_sensor: + - platform: haier + haier_id: haier_ac + compressor_status: + name: Haier Outdoor Compressor Status + defrost_status: + name: Haier Defrost Status + four_way_valve_status: + name: Haier Four Way Valve Status + indoor_electric_heating_status: + name: Haier Indoor Electric Heating Status + indoor_fan_status: + name: Haier Indoor Fan Status + outdoor_fan_status: + name: Haier Outdoor Fan Status diff --git a/tests/components/haier/test.esp32-idf.yaml b/tests/components/haier/test.esp32-idf.yaml new file mode 100644 index 0000000000..d3eeb04d65 --- /dev/null +++ b/tests/components/haier/test.esp32-idf.yaml @@ -0,0 +1,95 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_haier + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +climate: + - platform: haier + id: haier_ac + protocol: hOn + name: Haier AC + wifi_signal: true + answer_timeout: 200ms + beeper: true + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 + supported_modes: + - 'OFF' + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: "Alarm activated. Code: %d. Message: \"%s\"" + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: "Alarm deactivated. Code: %d. Message: \"%s\"" + args: [code, message] + +sensor: + - platform: haier + haier_id: haier_ac + outdoor_temperature: + name: Haier outdoor temperature + humidity: + name: Haier Indoor Humidity + compressor_current: + name: Haier Compressor Current + compressor_frequency: + name: Haier Compressor Frequency + expansion_valve_open_degree: + name: Haier Expansion Valve Open Degree + indoor_coil_temperature: + name: Haier Indoor Coil Temperature + outdoor_coil_temperature: + name: Haier Outdoor Coil Temperature + outdoor_defrost_temperature: + name: Haier Outdoor Defrost Temperature + outdoor_in_air_temperature: + name: Haier Outdoor In Air Temperature + outdoor_out_air_temperature: + name: Haier Outdoor Out Air Temperature + power: + name: Haier Power + +binary_sensor: + - platform: haier + haier_id: haier_ac + compressor_status: + name: Haier Outdoor Compressor Status + defrost_status: + name: Haier Defrost Status + four_way_valve_status: + name: Haier Four Way Valve Status + indoor_electric_heating_status: + name: Haier Indoor Electric Heating Status + indoor_fan_status: + name: Haier Indoor Fan Status + outdoor_fan_status: + name: Haier Outdoor Fan Status diff --git a/tests/components/haier/test.esp32.yaml b/tests/components/haier/test.esp32.yaml new file mode 100644 index 0000000000..d3eeb04d65 --- /dev/null +++ b/tests/components/haier/test.esp32.yaml @@ -0,0 +1,95 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_haier + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +climate: + - platform: haier + id: haier_ac + protocol: hOn + name: Haier AC + wifi_signal: true + answer_timeout: 200ms + beeper: true + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 + supported_modes: + - 'OFF' + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: "Alarm activated. Code: %d. Message: \"%s\"" + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: "Alarm deactivated. Code: %d. Message: \"%s\"" + args: [code, message] + +sensor: + - platform: haier + haier_id: haier_ac + outdoor_temperature: + name: Haier outdoor temperature + humidity: + name: Haier Indoor Humidity + compressor_current: + name: Haier Compressor Current + compressor_frequency: + name: Haier Compressor Frequency + expansion_valve_open_degree: + name: Haier Expansion Valve Open Degree + indoor_coil_temperature: + name: Haier Indoor Coil Temperature + outdoor_coil_temperature: + name: Haier Outdoor Coil Temperature + outdoor_defrost_temperature: + name: Haier Outdoor Defrost Temperature + outdoor_in_air_temperature: + name: Haier Outdoor In Air Temperature + outdoor_out_air_temperature: + name: Haier Outdoor Out Air Temperature + power: + name: Haier Power + +binary_sensor: + - platform: haier + haier_id: haier_ac + compressor_status: + name: Haier Outdoor Compressor Status + defrost_status: + name: Haier Defrost Status + four_way_valve_status: + name: Haier Four Way Valve Status + indoor_electric_heating_status: + name: Haier Indoor Electric Heating Status + indoor_fan_status: + name: Haier Indoor Fan Status + outdoor_fan_status: + name: Haier Outdoor Fan Status diff --git a/tests/components/haier/test.esp8266.yaml b/tests/components/haier/test.esp8266.yaml new file mode 100644 index 0000000000..72cfb781a7 --- /dev/null +++ b/tests/components/haier/test.esp8266.yaml @@ -0,0 +1,95 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_haier + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +climate: + - platform: haier + id: haier_ac + protocol: hOn + name: Haier AC + wifi_signal: true + answer_timeout: 200ms + beeper: true + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 + supported_modes: + - 'OFF' + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: "Alarm activated. Code: %d. Message: \"%s\"" + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: "Alarm deactivated. Code: %d. Message: \"%s\"" + args: [code, message] + +sensor: + - platform: haier + haier_id: haier_ac + outdoor_temperature: + name: Haier outdoor temperature + humidity: + name: Haier Indoor Humidity + compressor_current: + name: Haier Compressor Current + compressor_frequency: + name: Haier Compressor Frequency + expansion_valve_open_degree: + name: Haier Expansion Valve Open Degree + indoor_coil_temperature: + name: Haier Indoor Coil Temperature + outdoor_coil_temperature: + name: Haier Outdoor Coil Temperature + outdoor_defrost_temperature: + name: Haier Outdoor Defrost Temperature + outdoor_in_air_temperature: + name: Haier Outdoor In Air Temperature + outdoor_out_air_temperature: + name: Haier Outdoor Out Air Temperature + power: + name: Haier Power + +binary_sensor: + - platform: haier + haier_id: haier_ac + compressor_status: + name: Haier Outdoor Compressor Status + defrost_status: + name: Haier Defrost Status + four_way_valve_status: + name: Haier Four Way Valve Status + indoor_electric_heating_status: + name: Haier Indoor Electric Heating Status + indoor_fan_status: + name: Haier Indoor Fan Status + outdoor_fan_status: + name: Haier Outdoor Fan Status diff --git a/tests/components/haier/test.rp2040.yaml b/tests/components/haier/test.rp2040.yaml new file mode 100644 index 0000000000..72cfb781a7 --- /dev/null +++ b/tests/components/haier/test.rp2040.yaml @@ -0,0 +1,95 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_haier + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +climate: + - platform: haier + id: haier_ac + protocol: hOn + name: Haier AC + wifi_signal: true + answer_timeout: 200ms + beeper: true + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 + supported_modes: + - 'OFF' + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: "Alarm activated. Code: %d. Message: \"%s\"" + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: "Alarm deactivated. Code: %d. Message: \"%s\"" + args: [code, message] + +sensor: + - platform: haier + haier_id: haier_ac + outdoor_temperature: + name: Haier outdoor temperature + humidity: + name: Haier Indoor Humidity + compressor_current: + name: Haier Compressor Current + compressor_frequency: + name: Haier Compressor Frequency + expansion_valve_open_degree: + name: Haier Expansion Valve Open Degree + indoor_coil_temperature: + name: Haier Indoor Coil Temperature + outdoor_coil_temperature: + name: Haier Outdoor Coil Temperature + outdoor_defrost_temperature: + name: Haier Outdoor Defrost Temperature + outdoor_in_air_temperature: + name: Haier Outdoor In Air Temperature + outdoor_out_air_temperature: + name: Haier Outdoor Out Air Temperature + power: + name: Haier Power + +binary_sensor: + - platform: haier + haier_id: haier_ac + compressor_status: + name: Haier Outdoor Compressor Status + defrost_status: + name: Haier Defrost Status + four_way_valve_status: + name: Haier Four Way Valve Status + indoor_electric_heating_status: + name: Haier Indoor Electric Heating Status + indoor_fan_status: + name: Haier Indoor Fan Status + outdoor_fan_status: + name: Haier Outdoor Fan Status diff --git a/tests/components/htu31d/common.yaml b/tests/components/htu31d/common.yaml new file mode 100644 index 0000000000..c8ef2fede9 --- /dev/null +++ b/tests/components/htu31d/common.yaml @@ -0,0 +1,9 @@ +i2c: + +sensor: + - platform: htu31d + temperature: + name: Living Room Temperature 7 + humidity: + name: Living Room Humidity 7 + update_interval: 15s diff --git a/tests/components/htu31d/test.esp32-idf.yaml b/tests/components/htu31d/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/htu31d/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/htu31d/test.esp32.yaml b/tests/components/htu31d/test.esp32.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/htu31d/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/htu31d/test.esp8266.yaml b/tests/components/htu31d/test.esp8266.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/htu31d/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ili9xxx/test.esp32.yaml b/tests/components/ili9xxx/test.esp32.yaml new file mode 100644 index 0000000000..1095d565d2 --- /dev/null +++ b/tests/components/ili9xxx/test.esp32.yaml @@ -0,0 +1,11 @@ +spi: + mosi_pin: GPIO23 + clk_pin: GPIO18 + +display: + - platform: ili9xxx + model: gc9a01a + id: gca901_display + cs_pin: GPIO5 + dc_pin: GPIO22 + reset_pin: GPIO21 diff --git a/tests/components/jsn_sr04t/test.esp32-c3-idf.yaml b/tests/components/jsn_sr04t/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..5a37418a7d --- /dev/null +++ b/tests/components/jsn_sr04t/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_jsn_sr04t + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: jsn_sr04t + id: jsn_sr04t_sensor + name: "jsn_sr04t Distance" + uart_id: uart_jsn_sr04t + update_interval: 1s diff --git a/tests/components/jsn_sr04t/test.esp32-c3.yaml b/tests/components/jsn_sr04t/test.esp32-c3.yaml new file mode 100644 index 0000000000..5a37418a7d --- /dev/null +++ b/tests/components/jsn_sr04t/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_jsn_sr04t + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: jsn_sr04t + id: jsn_sr04t_sensor + name: "jsn_sr04t Distance" + uart_id: uart_jsn_sr04t + update_interval: 1s diff --git a/tests/components/jsn_sr04t/test.esp32-idf.yaml b/tests/components/jsn_sr04t/test.esp32-idf.yaml new file mode 100644 index 0000000000..32b4221b3f --- /dev/null +++ b/tests/components/jsn_sr04t/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_jsn_sr04t + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: jsn_sr04t + id: jsn_sr04t_sensor + name: "jsn_sr04t Distance" + uart_id: uart_jsn_sr04t + update_interval: 1s diff --git a/tests/components/jsn_sr04t/test.esp32.yaml b/tests/components/jsn_sr04t/test.esp32.yaml new file mode 100644 index 0000000000..32b4221b3f --- /dev/null +++ b/tests/components/jsn_sr04t/test.esp32.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_jsn_sr04t + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: jsn_sr04t + id: jsn_sr04t_sensor + name: "jsn_sr04t Distance" + uart_id: uart_jsn_sr04t + update_interval: 1s diff --git a/tests/components/jsn_sr04t/test.esp8266.yaml b/tests/components/jsn_sr04t/test.esp8266.yaml new file mode 100644 index 0000000000..5a37418a7d --- /dev/null +++ b/tests/components/jsn_sr04t/test.esp8266.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_jsn_sr04t + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: jsn_sr04t + id: jsn_sr04t_sensor + name: "jsn_sr04t Distance" + uart_id: uart_jsn_sr04t + update_interval: 1s diff --git a/tests/components/jsn_sr04t/test.rp2040.yaml b/tests/components/jsn_sr04t/test.rp2040.yaml new file mode 100644 index 0000000000..5a37418a7d --- /dev/null +++ b/tests/components/jsn_sr04t/test.rp2040.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_jsn_sr04t + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: jsn_sr04t + id: jsn_sr04t_sensor + name: "jsn_sr04t Distance" + uart_id: uart_jsn_sr04t + update_interval: 1s diff --git a/tests/components/kamstrup_kmp/common.yaml b/tests/components/kamstrup_kmp/common.yaml new file mode 100644 index 0000000000..b348d03c72 --- /dev/null +++ b/tests/components/kamstrup_kmp/common.yaml @@ -0,0 +1,25 @@ +uart: + tx_pin: ${uart_tx_pin} + rx_pin: ${uart_rx_pin} + baud_rate: 1200 + stop_bits: 2 + +sensor: + - platform: kamstrup_kmp + heat_energy: + name: Heat Energy + power: + name: Power + temp1: + name: Temperature 1 + temp2: + name: Temperature 2 + temp_diff: + name: Temperature Difference + flow: + name: Flow + volume: + name: Volume + custom: + - name: Custom 1 + command: 0x1234 diff --git a/tests/components/kamstrup_kmp/test.esp32-idf.yaml b/tests/components/kamstrup_kmp/test.esp32-idf.yaml new file mode 100644 index 0000000000..adc2c4d24a --- /dev/null +++ b/tests/components/kamstrup_kmp/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO1 + uart_rx_pin: GPIO3 + +<<: !include common.yaml diff --git a/tests/components/kamstrup_kmp/test.esp32.yaml b/tests/components/kamstrup_kmp/test.esp32.yaml new file mode 100644 index 0000000000..adc2c4d24a --- /dev/null +++ b/tests/components/kamstrup_kmp/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO1 + uart_rx_pin: GPIO3 + +<<: !include common.yaml diff --git a/tests/components/kamstrup_kmp/test.esp8266.yaml b/tests/components/kamstrup_kmp/test.esp8266.yaml new file mode 100644 index 0000000000..adc2c4d24a --- /dev/null +++ b/tests/components/kamstrup_kmp/test.esp8266.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO1 + uart_rx_pin: GPIO3 + +<<: !include common.yaml diff --git a/tests/components/key_collector/test.esp32-c3-idf.yaml b/tests/components/key_collector/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..1f133c5cd8 --- /dev/null +++ b/tests/components/key_collector/test.esp32-c3-idf.yaml @@ -0,0 +1,28 @@ +matrix_keypad: + id: keypad + rows: + - pin: 1 + - pin: 2 + columns: + - pin: 3 + - pin: 4 + keys: "1234" + has_pulldowns: true + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + on_progress: + - logger.log: + format: "input progress: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] + on_result: + - logger.log: + format: "input result: '%s', started by '%c', ended by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)"] + on_timeout: + - logger.log: + format: "input timeout: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] diff --git a/tests/components/key_collector/test.esp32-c3.yaml b/tests/components/key_collector/test.esp32-c3.yaml new file mode 100644 index 0000000000..1f133c5cd8 --- /dev/null +++ b/tests/components/key_collector/test.esp32-c3.yaml @@ -0,0 +1,28 @@ +matrix_keypad: + id: keypad + rows: + - pin: 1 + - pin: 2 + columns: + - pin: 3 + - pin: 4 + keys: "1234" + has_pulldowns: true + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + on_progress: + - logger.log: + format: "input progress: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] + on_result: + - logger.log: + format: "input result: '%s', started by '%c', ended by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)"] + on_timeout: + - logger.log: + format: "input timeout: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] diff --git a/tests/components/key_collector/test.esp32-idf.yaml b/tests/components/key_collector/test.esp32-idf.yaml new file mode 100644 index 0000000000..7cbe9c0fc1 --- /dev/null +++ b/tests/components/key_collector/test.esp32-idf.yaml @@ -0,0 +1,28 @@ +matrix_keypad: + id: keypad + rows: + - pin: 12 + - pin: 13 + columns: + - pin: 14 + - pin: 15 + keys: "1234" + has_pulldowns: true + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + on_progress: + - logger.log: + format: "input progress: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] + on_result: + - logger.log: + format: "input result: '%s', started by '%c', ended by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)"] + on_timeout: + - logger.log: + format: "input timeout: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] diff --git a/tests/components/key_collector/test.esp32.yaml b/tests/components/key_collector/test.esp32.yaml new file mode 100644 index 0000000000..7cbe9c0fc1 --- /dev/null +++ b/tests/components/key_collector/test.esp32.yaml @@ -0,0 +1,28 @@ +matrix_keypad: + id: keypad + rows: + - pin: 12 + - pin: 13 + columns: + - pin: 14 + - pin: 15 + keys: "1234" + has_pulldowns: true + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + on_progress: + - logger.log: + format: "input progress: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] + on_result: + - logger.log: + format: "input result: '%s', started by '%c', ended by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)"] + on_timeout: + - logger.log: + format: "input timeout: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] diff --git a/tests/components/key_collector/test.esp8266.yaml b/tests/components/key_collector/test.esp8266.yaml new file mode 100644 index 0000000000..7cbe9c0fc1 --- /dev/null +++ b/tests/components/key_collector/test.esp8266.yaml @@ -0,0 +1,28 @@ +matrix_keypad: + id: keypad + rows: + - pin: 12 + - pin: 13 + columns: + - pin: 14 + - pin: 15 + keys: "1234" + has_pulldowns: true + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + on_progress: + - logger.log: + format: "input progress: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] + on_result: + - logger.log: + format: "input result: '%s', started by '%c', ended by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)"] + on_timeout: + - logger.log: + format: "input timeout: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] diff --git a/tests/components/key_collector/test.rp2040.yaml b/tests/components/key_collector/test.rp2040.yaml new file mode 100644 index 0000000000..1f133c5cd8 --- /dev/null +++ b/tests/components/key_collector/test.rp2040.yaml @@ -0,0 +1,28 @@ +matrix_keypad: + id: keypad + rows: + - pin: 1 + - pin: 2 + columns: + - pin: 3 + - pin: 4 + keys: "1234" + has_pulldowns: true + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + on_progress: + - logger.log: + format: "input progress: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] + on_result: + - logger.log: + format: "input result: '%s', started by '%c', ended by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)"] + on_timeout: + - logger.log: + format: "input timeout: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] diff --git a/tests/components/kmeteriso/test.esp32-c3-idf.yaml b/tests/components/kmeteriso/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..7780cfea32 --- /dev/null +++ b/tests/components/kmeteriso/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_kmeteriso + scl: 5 + sda: 4 + +sensor: + - platform: kmeteriso + temperature: + name: Outside Temperature + internal_temperature: + name: Internal Temperature + update_interval: 15s diff --git a/tests/components/kmeteriso/test.esp32-c3.yaml b/tests/components/kmeteriso/test.esp32-c3.yaml new file mode 100644 index 0000000000..7780cfea32 --- /dev/null +++ b/tests/components/kmeteriso/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_kmeteriso + scl: 5 + sda: 4 + +sensor: + - platform: kmeteriso + temperature: + name: Outside Temperature + internal_temperature: + name: Internal Temperature + update_interval: 15s diff --git a/tests/components/kmeteriso/test.esp32-idf.yaml b/tests/components/kmeteriso/test.esp32-idf.yaml new file mode 100644 index 0000000000..2c375dda31 --- /dev/null +++ b/tests/components/kmeteriso/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_kmeteriso + scl: 16 + sda: 17 + +sensor: + - platform: kmeteriso + temperature: + name: Outside Temperature + internal_temperature: + name: Internal Temperature + update_interval: 15s diff --git a/tests/components/kmeteriso/test.esp32.yaml b/tests/components/kmeteriso/test.esp32.yaml new file mode 100644 index 0000000000..2c375dda31 --- /dev/null +++ b/tests/components/kmeteriso/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_kmeteriso + scl: 16 + sda: 17 + +sensor: + - platform: kmeteriso + temperature: + name: Outside Temperature + internal_temperature: + name: Internal Temperature + update_interval: 15s diff --git a/tests/components/kmeteriso/test.esp8266.yaml b/tests/components/kmeteriso/test.esp8266.yaml new file mode 100644 index 0000000000..7780cfea32 --- /dev/null +++ b/tests/components/kmeteriso/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_kmeteriso + scl: 5 + sda: 4 + +sensor: + - platform: kmeteriso + temperature: + name: Outside Temperature + internal_temperature: + name: Internal Temperature + update_interval: 15s diff --git a/tests/components/kmeteriso/test.rp2040.yaml b/tests/components/kmeteriso/test.rp2040.yaml new file mode 100644 index 0000000000..7780cfea32 --- /dev/null +++ b/tests/components/kmeteriso/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_kmeteriso + scl: 5 + sda: 4 + +sensor: + - platform: kmeteriso + temperature: + name: Outside Temperature + internal_temperature: + name: Internal Temperature + update_interval: 15s diff --git a/tests/components/kuntze/test.esp32-c3-idf.yaml b/tests/components/kuntze/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..08278c3c82 --- /dev/null +++ b/tests/components/kuntze/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +uart: + - id: uart_kuntze + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature diff --git a/tests/components/kuntze/test.esp32-c3.yaml b/tests/components/kuntze/test.esp32-c3.yaml new file mode 100644 index 0000000000..08278c3c82 --- /dev/null +++ b/tests/components/kuntze/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +uart: + - id: uart_kuntze + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature diff --git a/tests/components/kuntze/test.esp32-idf.yaml b/tests/components/kuntze/test.esp32-idf.yaml new file mode 100644 index 0000000000..6b6c638971 --- /dev/null +++ b/tests/components/kuntze/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +uart: + - id: uart_kuntze + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + flow_control_pin: 13 + +sensor: + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature diff --git a/tests/components/kuntze/test.esp32.yaml b/tests/components/kuntze/test.esp32.yaml new file mode 100644 index 0000000000..6b6c638971 --- /dev/null +++ b/tests/components/kuntze/test.esp32.yaml @@ -0,0 +1,15 @@ +uart: + - id: uart_kuntze + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + flow_control_pin: 13 + +sensor: + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature diff --git a/tests/components/kuntze/test.esp8266.yaml b/tests/components/kuntze/test.esp8266.yaml new file mode 100644 index 0000000000..eba6cddc2d --- /dev/null +++ b/tests/components/kuntze/test.esp8266.yaml @@ -0,0 +1,15 @@ +uart: + - id: uart_kuntze + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 13 + +sensor: + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature diff --git a/tests/components/kuntze/test.rp2040.yaml b/tests/components/kuntze/test.rp2040.yaml new file mode 100644 index 0000000000..08278c3c82 --- /dev/null +++ b/tests/components/kuntze/test.rp2040.yaml @@ -0,0 +1,15 @@ +uart: + - id: uart_kuntze + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature diff --git a/tests/components/lcd_gpio/test.esp32-c3-idf.yaml b/tests/components/lcd_gpio/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..b89715a755 --- /dev/null +++ b/tests/components/lcd_gpio/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 1 + - number: 2 + - number: 3 + - number: 4 + enable_pin: 5 + rs_pin: 6 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_gpio/test.esp32-c3.yaml b/tests/components/lcd_gpio/test.esp32-c3.yaml new file mode 100644 index 0000000000..b89715a755 --- /dev/null +++ b/tests/components/lcd_gpio/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 1 + - number: 2 + - number: 3 + - number: 4 + enable_pin: 5 + rs_pin: 6 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_gpio/test.esp32-idf.yaml b/tests/components/lcd_gpio/test.esp32-idf.yaml new file mode 100644 index 0000000000..d2b33aeb3a --- /dev/null +++ b/tests/components/lcd_gpio/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 12 + - number: 13 + - number: 14 + - number: 15 + enable_pin: 16 + rs_pin: 5 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_gpio/test.esp32.yaml b/tests/components/lcd_gpio/test.esp32.yaml new file mode 100644 index 0000000000..d2b33aeb3a --- /dev/null +++ b/tests/components/lcd_gpio/test.esp32.yaml @@ -0,0 +1,13 @@ +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 12 + - number: 13 + - number: 14 + - number: 15 + enable_pin: 16 + rs_pin: 5 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_gpio/test.esp8266.yaml b/tests/components/lcd_gpio/test.esp8266.yaml new file mode 100644 index 0000000000..d2b33aeb3a --- /dev/null +++ b/tests/components/lcd_gpio/test.esp8266.yaml @@ -0,0 +1,13 @@ +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 12 + - number: 13 + - number: 14 + - number: 15 + enable_pin: 16 + rs_pin: 5 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_gpio/test.rp2040.yaml b/tests/components/lcd_gpio/test.rp2040.yaml new file mode 100644 index 0000000000..b89715a755 --- /dev/null +++ b/tests/components/lcd_gpio/test.rp2040.yaml @@ -0,0 +1,13 @@ +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 1 + - number: 2 + - number: 3 + - number: 4 + enable_pin: 5 + rs_pin: 6 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_menu/test.esp32-c3-idf.yaml b/tests/components/lcd_menu/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..39d2278d3d --- /dev/null +++ b/tests/components/lcd_menu/test.esp32-c3-idf.yaml @@ -0,0 +1,118 @@ +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + name: Template Switch + id: my_switch + optimistic: true + +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 1 + - number: 2 + - number: 3 + - number: 4 + enable_pin: 5 + rs_pin: 6 + lambda: |- + it.print("Hello World!"); + +lcd_menu: + id: test_lcd_menu + display_id: my_lcd_gpio + mark_back: 0x5e + mark_selected: 0x3e + mark_editing: 0x2a + mark_submenu: 0x7e + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "root leave");' + items: + - type: back + text: Back + - type: label + - type: menu + text: Submenu 1 + items: + - type: back + text: Back + - type: menu + text: Submenu 21 + items: + - type: back + text: Back + - type: command + text: Show Main + on_value: + then: + - display_menu.show_main: test_lcd_menu + - type: select + text: Enum Item + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: Number + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: Hide + on_value: + then: + - display_menu.hide: test_lcd_menu + - type: switch + text: Switch + switch: my_switch + on_text: Bright + off_text: Dark + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/lcd_menu/test.esp32-c3.yaml b/tests/components/lcd_menu/test.esp32-c3.yaml new file mode 100644 index 0000000000..39d2278d3d --- /dev/null +++ b/tests/components/lcd_menu/test.esp32-c3.yaml @@ -0,0 +1,118 @@ +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + name: Template Switch + id: my_switch + optimistic: true + +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 1 + - number: 2 + - number: 3 + - number: 4 + enable_pin: 5 + rs_pin: 6 + lambda: |- + it.print("Hello World!"); + +lcd_menu: + id: test_lcd_menu + display_id: my_lcd_gpio + mark_back: 0x5e + mark_selected: 0x3e + mark_editing: 0x2a + mark_submenu: 0x7e + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "root leave");' + items: + - type: back + text: Back + - type: label + - type: menu + text: Submenu 1 + items: + - type: back + text: Back + - type: menu + text: Submenu 21 + items: + - type: back + text: Back + - type: command + text: Show Main + on_value: + then: + - display_menu.show_main: test_lcd_menu + - type: select + text: Enum Item + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: Number + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: Hide + on_value: + then: + - display_menu.hide: test_lcd_menu + - type: switch + text: Switch + switch: my_switch + on_text: Bright + off_text: Dark + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/lcd_menu/test.esp32-idf.yaml b/tests/components/lcd_menu/test.esp32-idf.yaml new file mode 100644 index 0000000000..833ea2169a --- /dev/null +++ b/tests/components/lcd_menu/test.esp32-idf.yaml @@ -0,0 +1,118 @@ +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + name: Template Switch + id: my_switch + optimistic: true + +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 12 + - number: 13 + - number: 14 + - number: 15 + enable_pin: 16 + rs_pin: 5 + lambda: |- + it.print("Hello World!"); + +lcd_menu: + id: test_lcd_menu + display_id: my_lcd_gpio + mark_back: 0x5e + mark_selected: 0x3e + mark_editing: 0x2a + mark_submenu: 0x7e + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "root leave");' + items: + - type: back + text: Back + - type: label + - type: menu + text: Submenu 1 + items: + - type: back + text: Back + - type: menu + text: Submenu 21 + items: + - type: back + text: Back + - type: command + text: Show Main + on_value: + then: + - display_menu.show_main: test_lcd_menu + - type: select + text: Enum Item + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: Number + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: Hide + on_value: + then: + - display_menu.hide: test_lcd_menu + - type: switch + text: Switch + switch: my_switch + on_text: Bright + off_text: Dark + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/lcd_menu/test.esp32.yaml b/tests/components/lcd_menu/test.esp32.yaml new file mode 100644 index 0000000000..833ea2169a --- /dev/null +++ b/tests/components/lcd_menu/test.esp32.yaml @@ -0,0 +1,118 @@ +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + name: Template Switch + id: my_switch + optimistic: true + +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 12 + - number: 13 + - number: 14 + - number: 15 + enable_pin: 16 + rs_pin: 5 + lambda: |- + it.print("Hello World!"); + +lcd_menu: + id: test_lcd_menu + display_id: my_lcd_gpio + mark_back: 0x5e + mark_selected: 0x3e + mark_editing: 0x2a + mark_submenu: 0x7e + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "root leave");' + items: + - type: back + text: Back + - type: label + - type: menu + text: Submenu 1 + items: + - type: back + text: Back + - type: menu + text: Submenu 21 + items: + - type: back + text: Back + - type: command + text: Show Main + on_value: + then: + - display_menu.show_main: test_lcd_menu + - type: select + text: Enum Item + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: Number + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: Hide + on_value: + then: + - display_menu.hide: test_lcd_menu + - type: switch + text: Switch + switch: my_switch + on_text: Bright + off_text: Dark + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/lcd_menu/test.esp8266.yaml b/tests/components/lcd_menu/test.esp8266.yaml new file mode 100644 index 0000000000..833ea2169a --- /dev/null +++ b/tests/components/lcd_menu/test.esp8266.yaml @@ -0,0 +1,118 @@ +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + name: Template Switch + id: my_switch + optimistic: true + +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 12 + - number: 13 + - number: 14 + - number: 15 + enable_pin: 16 + rs_pin: 5 + lambda: |- + it.print("Hello World!"); + +lcd_menu: + id: test_lcd_menu + display_id: my_lcd_gpio + mark_back: 0x5e + mark_selected: 0x3e + mark_editing: 0x2a + mark_submenu: 0x7e + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "root leave");' + items: + - type: back + text: Back + - type: label + - type: menu + text: Submenu 1 + items: + - type: back + text: Back + - type: menu + text: Submenu 21 + items: + - type: back + text: Back + - type: command + text: Show Main + on_value: + then: + - display_menu.show_main: test_lcd_menu + - type: select + text: Enum Item + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: Number + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: Hide + on_value: + then: + - display_menu.hide: test_lcd_menu + - type: switch + text: Switch + switch: my_switch + on_text: Bright + off_text: Dark + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/lcd_menu/test.rp2040.yaml b/tests/components/lcd_menu/test.rp2040.yaml new file mode 100644 index 0000000000..39d2278d3d --- /dev/null +++ b/tests/components/lcd_menu/test.rp2040.yaml @@ -0,0 +1,118 @@ +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + name: Template Switch + id: my_switch + optimistic: true + +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 1 + - number: 2 + - number: 3 + - number: 4 + enable_pin: 5 + rs_pin: 6 + lambda: |- + it.print("Hello World!"); + +lcd_menu: + id: test_lcd_menu + display_id: my_lcd_gpio + mark_back: 0x5e + mark_selected: 0x3e + mark_editing: 0x2a + mark_submenu: 0x7e + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "root leave");' + items: + - type: back + text: Back + - type: label + - type: menu + text: Submenu 1 + items: + - type: back + text: Back + - type: menu + text: Submenu 21 + items: + - type: back + text: Back + - type: command + text: Show Main + on_value: + then: + - display_menu.show_main: test_lcd_menu + - type: select + text: Enum Item + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: Number + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: Hide + on_value: + then: + - display_menu.hide: test_lcd_menu + - type: switch + text: Switch + switch: my_switch + on_text: Bright + off_text: Dark + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/lcd_pcf8574/test.esp32-c3-idf.yaml b/tests/components/lcd_pcf8574/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..41eba26950 --- /dev/null +++ b/tests/components/lcd_pcf8574/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_lcd_pcf8574 + scl: 5 + sda: 4 + +display: + - platform: lcd_pcf8574 + dimensions: 18x4 + address: 0x3F + user_characters: + - position: 0 + data: + - 0b00000 + - 0b01010 + - 0b00000 + - 0b00100 + - 0b00100 + - 0b10001 + - 0b01110 + - 0b00000 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_pcf8574/test.esp32-c3.yaml b/tests/components/lcd_pcf8574/test.esp32-c3.yaml new file mode 100644 index 0000000000..41eba26950 --- /dev/null +++ b/tests/components/lcd_pcf8574/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_lcd_pcf8574 + scl: 5 + sda: 4 + +display: + - platform: lcd_pcf8574 + dimensions: 18x4 + address: 0x3F + user_characters: + - position: 0 + data: + - 0b00000 + - 0b01010 + - 0b00000 + - 0b00100 + - 0b00100 + - 0b10001 + - 0b01110 + - 0b00000 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_pcf8574/test.esp32-idf.yaml b/tests/components/lcd_pcf8574/test.esp32-idf.yaml new file mode 100644 index 0000000000..9d7d475f30 --- /dev/null +++ b/tests/components/lcd_pcf8574/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_lcd_pcf8574 + scl: 16 + sda: 17 + +display: + - platform: lcd_pcf8574 + dimensions: 18x4 + address: 0x3F + user_characters: + - position: 0 + data: + - 0b00000 + - 0b01010 + - 0b00000 + - 0b00100 + - 0b00100 + - 0b10001 + - 0b01110 + - 0b00000 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_pcf8574/test.esp32.yaml b/tests/components/lcd_pcf8574/test.esp32.yaml new file mode 100644 index 0000000000..9d7d475f30 --- /dev/null +++ b/tests/components/lcd_pcf8574/test.esp32.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_lcd_pcf8574 + scl: 16 + sda: 17 + +display: + - platform: lcd_pcf8574 + dimensions: 18x4 + address: 0x3F + user_characters: + - position: 0 + data: + - 0b00000 + - 0b01010 + - 0b00000 + - 0b00100 + - 0b00100 + - 0b10001 + - 0b01110 + - 0b00000 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_pcf8574/test.esp8266.yaml b/tests/components/lcd_pcf8574/test.esp8266.yaml new file mode 100644 index 0000000000..41eba26950 --- /dev/null +++ b/tests/components/lcd_pcf8574/test.esp8266.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_lcd_pcf8574 + scl: 5 + sda: 4 + +display: + - platform: lcd_pcf8574 + dimensions: 18x4 + address: 0x3F + user_characters: + - position: 0 + data: + - 0b00000 + - 0b01010 + - 0b00000 + - 0b00100 + - 0b00100 + - 0b10001 + - 0b01110 + - 0b00000 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_pcf8574/test.rp2040.yaml b/tests/components/lcd_pcf8574/test.rp2040.yaml new file mode 100644 index 0000000000..41eba26950 --- /dev/null +++ b/tests/components/lcd_pcf8574/test.rp2040.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_lcd_pcf8574 + scl: 5 + sda: 4 + +display: + - platform: lcd_pcf8574 + dimensions: 18x4 + address: 0x3F + user_characters: + - position: 0 + data: + - 0b00000 + - 0b01010 + - 0b00000 + - 0b00100 + - 0b00100 + - 0b10001 + - 0b01110 + - 0b00000 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/ld2410/test.esp32-c3-idf.yaml b/tests/components/ld2410/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..afcaa81b3e --- /dev/null +++ b/tests/components/ld2410/test.esp32-c3-idf.yaml @@ -0,0 +1,169 @@ +uart: + - id: uart_ld2410 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2410: + id: my_ld2410 + +binary_sensor: + - platform: ld2410 + has_target: + name: presence + has_moving_target: + name: movement + has_still_target: + name: still + out_pin_presence_status: + name: out pin presence status + +button: + - platform: ld2410 + factory_reset: + name: factory reset + restart: + name: restart + query_params: + name: query params + +number: + - platform: ld2410 + light_threshold: + name: light threshold + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out ping level + +sensor: + - platform: ld2410 + light: + name: light + moving_distance: + name: Moving distance + still_distance: + name: Still Distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Distance Detection + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + +switch: + - platform: ld2410 + engineering_mode: + name: control ld2410 engineering mode + bluetooth: + name: control ld2410 bluetooth + +text_sensor: + - platform: ld2410 + version: + name: presenece sensor version + mac_address: + name: presenece sensor mac address diff --git a/tests/components/ld2410/test.esp32-c3.yaml b/tests/components/ld2410/test.esp32-c3.yaml new file mode 100644 index 0000000000..afcaa81b3e --- /dev/null +++ b/tests/components/ld2410/test.esp32-c3.yaml @@ -0,0 +1,169 @@ +uart: + - id: uart_ld2410 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2410: + id: my_ld2410 + +binary_sensor: + - platform: ld2410 + has_target: + name: presence + has_moving_target: + name: movement + has_still_target: + name: still + out_pin_presence_status: + name: out pin presence status + +button: + - platform: ld2410 + factory_reset: + name: factory reset + restart: + name: restart + query_params: + name: query params + +number: + - platform: ld2410 + light_threshold: + name: light threshold + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out ping level + +sensor: + - platform: ld2410 + light: + name: light + moving_distance: + name: Moving distance + still_distance: + name: Still Distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Distance Detection + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + +switch: + - platform: ld2410 + engineering_mode: + name: control ld2410 engineering mode + bluetooth: + name: control ld2410 bluetooth + +text_sensor: + - platform: ld2410 + version: + name: presenece sensor version + mac_address: + name: presenece sensor mac address diff --git a/tests/components/ld2410/test.esp32-idf.yaml b/tests/components/ld2410/test.esp32-idf.yaml new file mode 100644 index 0000000000..48ed179d93 --- /dev/null +++ b/tests/components/ld2410/test.esp32-idf.yaml @@ -0,0 +1,169 @@ +uart: + - id: uart_ld2410 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +ld2410: + id: my_ld2410 + +binary_sensor: + - platform: ld2410 + has_target: + name: presence + has_moving_target: + name: movement + has_still_target: + name: still + out_pin_presence_status: + name: out pin presence status + +button: + - platform: ld2410 + factory_reset: + name: factory reset + restart: + name: restart + query_params: + name: query params + +number: + - platform: ld2410 + light_threshold: + name: light threshold + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out ping level + +sensor: + - platform: ld2410 + light: + name: light + moving_distance: + name: Moving distance + still_distance: + name: Still Distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Distance Detection + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + +switch: + - platform: ld2410 + engineering_mode: + name: control ld2410 engineering mode + bluetooth: + name: control ld2410 bluetooth + +text_sensor: + - platform: ld2410 + version: + name: presenece sensor version + mac_address: + name: presenece sensor mac address diff --git a/tests/components/ld2410/test.esp32.yaml b/tests/components/ld2410/test.esp32.yaml new file mode 100644 index 0000000000..48ed179d93 --- /dev/null +++ b/tests/components/ld2410/test.esp32.yaml @@ -0,0 +1,169 @@ +uart: + - id: uart_ld2410 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +ld2410: + id: my_ld2410 + +binary_sensor: + - platform: ld2410 + has_target: + name: presence + has_moving_target: + name: movement + has_still_target: + name: still + out_pin_presence_status: + name: out pin presence status + +button: + - platform: ld2410 + factory_reset: + name: factory reset + restart: + name: restart + query_params: + name: query params + +number: + - platform: ld2410 + light_threshold: + name: light threshold + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out ping level + +sensor: + - platform: ld2410 + light: + name: light + moving_distance: + name: Moving distance + still_distance: + name: Still Distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Distance Detection + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + +switch: + - platform: ld2410 + engineering_mode: + name: control ld2410 engineering mode + bluetooth: + name: control ld2410 bluetooth + +text_sensor: + - platform: ld2410 + version: + name: presenece sensor version + mac_address: + name: presenece sensor mac address diff --git a/tests/components/ld2410/test.esp8266.yaml b/tests/components/ld2410/test.esp8266.yaml new file mode 100644 index 0000000000..afcaa81b3e --- /dev/null +++ b/tests/components/ld2410/test.esp8266.yaml @@ -0,0 +1,169 @@ +uart: + - id: uart_ld2410 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2410: + id: my_ld2410 + +binary_sensor: + - platform: ld2410 + has_target: + name: presence + has_moving_target: + name: movement + has_still_target: + name: still + out_pin_presence_status: + name: out pin presence status + +button: + - platform: ld2410 + factory_reset: + name: factory reset + restart: + name: restart + query_params: + name: query params + +number: + - platform: ld2410 + light_threshold: + name: light threshold + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out ping level + +sensor: + - platform: ld2410 + light: + name: light + moving_distance: + name: Moving distance + still_distance: + name: Still Distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Distance Detection + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + +switch: + - platform: ld2410 + engineering_mode: + name: control ld2410 engineering mode + bluetooth: + name: control ld2410 bluetooth + +text_sensor: + - platform: ld2410 + version: + name: presenece sensor version + mac_address: + name: presenece sensor mac address diff --git a/tests/components/ld2410/test.rp2040.yaml b/tests/components/ld2410/test.rp2040.yaml new file mode 100644 index 0000000000..afcaa81b3e --- /dev/null +++ b/tests/components/ld2410/test.rp2040.yaml @@ -0,0 +1,169 @@ +uart: + - id: uart_ld2410 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2410: + id: my_ld2410 + +binary_sensor: + - platform: ld2410 + has_target: + name: presence + has_moving_target: + name: movement + has_still_target: + name: still + out_pin_presence_status: + name: out pin presence status + +button: + - platform: ld2410 + factory_reset: + name: factory reset + restart: + name: restart + query_params: + name: query params + +number: + - platform: ld2410 + light_threshold: + name: light threshold + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out ping level + +sensor: + - platform: ld2410 + light: + name: light + moving_distance: + name: Moving distance + still_distance: + name: Still Distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Distance Detection + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + +switch: + - platform: ld2410 + engineering_mode: + name: control ld2410 engineering mode + bluetooth: + name: control ld2410 bluetooth + +text_sensor: + - platform: ld2410 + version: + name: presenece sensor version + mac_address: + name: presenece sensor mac address diff --git a/tests/components/ld2420/test.esp32-c3-idf.yaml b/tests/components/ld2420/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..5e0b9db1c2 --- /dev/null +++ b/tests/components/ld2420/test.esp32-c3-idf.yaml @@ -0,0 +1,132 @@ +uart: + - id: uart_ld2420 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2420: + id: my_ld2420 + +binary_sensor: + - platform: ld2420 + has_target: + name: Presence + +button: + - platform: ld2420 + apply_config: + name: Apply Config + factory_reset: + name: Factory Reset + restart_module: + name: Restart Module + revert_config: + name: Undo Edits + +number: + - platform: ld2420 + presence_timeout: + name: Detection Presence Timeout + min_gate_distance: + name: Detection Gate Minimum + max_gate_distance: + name: Detection Gate Maximum + gate_move_sensitivity: + name: Move Calibration Sensitivity Factor + gate_still_sensitivity: + name: Still Calibration Sensitivity Factor + gate_0: + move_threshold: + name: Gate 0 Move Threshold + still_threshold: + name: Gate 0 Still Threshold + gate_1: + move_threshold: + name: Gate 1 Move Threshold + still_threshold: + name: Gate 1 Still Threshold + gate_2: + move_threshold: + name: Gate 2 Move Threshold + still_threshold: + name: Gate 2 Still Threshold + gate_3: + move_threshold: + name: Gate 3 Move Threshold + still_threshold: + name: Gate 3 Still Threshold + gate_4: + move_threshold: + name: Gate 4 Move Threshold + still_threshold: + name: Gate 4 Still Threshold + gate_5: + move_threshold: + name: Gate 5 Move Threshold + still_threshold: + name: Gate 5 Still Threshold + gate_6: + move_threshold: + name: Gate 6 Move Threshold + still_threshold: + name: Gate 6 Still Threshold + gate_7: + move_threshold: + name: Gate 7 Move Threshold + still_threshold: + name: Gate 7 Still Threshold + gate_8: + move_threshold: + name: Gate 8 Move Threshold + still_threshold: + name: Gate 8 Still Threshold + gate_9: + move_threshold: + name: Gate 9 Move Threshold + still_threshold: + name: Gate 9 Still Threshold + gate_10: + move_threshold: + name: Gate 10 Move Threshold + still_threshold: + name: Gate 10 Still Threshold + gate_11: + move_threshold: + name: Gate 11 Move Threshold + still_threshold: + name: Gate 11 Still Threshold + gate_12: + move_threshold: + name: Gate 12 Move Threshold + still_threshold: + name: Gate 12 Still Threshold + gate_13: + move_threshold: + name: Gate 13 Move Threshold + still_threshold: + name: Gate 13 Still Threshold + gate_14: + move_threshold: + name: Gate 14 Move Threshold + still_threshold: + name: Gate 14 Still Threshold + gate_15: + move_threshold: + name: Gate 15 Move Threshold + still_threshold: + name: Gate 15 Still Threshold + +select: + - platform: ld2420 + operating_mode: + name: Operating Mode + +sensor: + - platform: ld2420 + moving_distance: + name: "Moving distance (cm)" + +text_sensor: + - platform: ld2420 + fw_version: + name: LD2420 Firmware diff --git a/tests/components/ld2420/test.esp32-c3.yaml b/tests/components/ld2420/test.esp32-c3.yaml new file mode 100644 index 0000000000..5e0b9db1c2 --- /dev/null +++ b/tests/components/ld2420/test.esp32-c3.yaml @@ -0,0 +1,132 @@ +uart: + - id: uart_ld2420 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2420: + id: my_ld2420 + +binary_sensor: + - platform: ld2420 + has_target: + name: Presence + +button: + - platform: ld2420 + apply_config: + name: Apply Config + factory_reset: + name: Factory Reset + restart_module: + name: Restart Module + revert_config: + name: Undo Edits + +number: + - platform: ld2420 + presence_timeout: + name: Detection Presence Timeout + min_gate_distance: + name: Detection Gate Minimum + max_gate_distance: + name: Detection Gate Maximum + gate_move_sensitivity: + name: Move Calibration Sensitivity Factor + gate_still_sensitivity: + name: Still Calibration Sensitivity Factor + gate_0: + move_threshold: + name: Gate 0 Move Threshold + still_threshold: + name: Gate 0 Still Threshold + gate_1: + move_threshold: + name: Gate 1 Move Threshold + still_threshold: + name: Gate 1 Still Threshold + gate_2: + move_threshold: + name: Gate 2 Move Threshold + still_threshold: + name: Gate 2 Still Threshold + gate_3: + move_threshold: + name: Gate 3 Move Threshold + still_threshold: + name: Gate 3 Still Threshold + gate_4: + move_threshold: + name: Gate 4 Move Threshold + still_threshold: + name: Gate 4 Still Threshold + gate_5: + move_threshold: + name: Gate 5 Move Threshold + still_threshold: + name: Gate 5 Still Threshold + gate_6: + move_threshold: + name: Gate 6 Move Threshold + still_threshold: + name: Gate 6 Still Threshold + gate_7: + move_threshold: + name: Gate 7 Move Threshold + still_threshold: + name: Gate 7 Still Threshold + gate_8: + move_threshold: + name: Gate 8 Move Threshold + still_threshold: + name: Gate 8 Still Threshold + gate_9: + move_threshold: + name: Gate 9 Move Threshold + still_threshold: + name: Gate 9 Still Threshold + gate_10: + move_threshold: + name: Gate 10 Move Threshold + still_threshold: + name: Gate 10 Still Threshold + gate_11: + move_threshold: + name: Gate 11 Move Threshold + still_threshold: + name: Gate 11 Still Threshold + gate_12: + move_threshold: + name: Gate 12 Move Threshold + still_threshold: + name: Gate 12 Still Threshold + gate_13: + move_threshold: + name: Gate 13 Move Threshold + still_threshold: + name: Gate 13 Still Threshold + gate_14: + move_threshold: + name: Gate 14 Move Threshold + still_threshold: + name: Gate 14 Still Threshold + gate_15: + move_threshold: + name: Gate 15 Move Threshold + still_threshold: + name: Gate 15 Still Threshold + +select: + - platform: ld2420 + operating_mode: + name: Operating Mode + +sensor: + - platform: ld2420 + moving_distance: + name: "Moving distance (cm)" + +text_sensor: + - platform: ld2420 + fw_version: + name: LD2420 Firmware diff --git a/tests/components/ld2420/test.esp32-idf.yaml b/tests/components/ld2420/test.esp32-idf.yaml new file mode 100644 index 0000000000..8c883664ec --- /dev/null +++ b/tests/components/ld2420/test.esp32-idf.yaml @@ -0,0 +1,132 @@ +uart: + - id: uart_ld2420 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +ld2420: + id: my_ld2420 + +binary_sensor: + - platform: ld2420 + has_target: + name: Presence + +button: + - platform: ld2420 + apply_config: + name: Apply Config + factory_reset: + name: Factory Reset + restart_module: + name: Restart Module + revert_config: + name: Undo Edits + +number: + - platform: ld2420 + presence_timeout: + name: Detection Presence Timeout + min_gate_distance: + name: Detection Gate Minimum + max_gate_distance: + name: Detection Gate Maximum + gate_move_sensitivity: + name: Move Calibration Sensitivity Factor + gate_still_sensitivity: + name: Still Calibration Sensitivity Factor + gate_0: + move_threshold: + name: Gate 0 Move Threshold + still_threshold: + name: Gate 0 Still Threshold + gate_1: + move_threshold: + name: Gate 1 Move Threshold + still_threshold: + name: Gate 1 Still Threshold + gate_2: + move_threshold: + name: Gate 2 Move Threshold + still_threshold: + name: Gate 2 Still Threshold + gate_3: + move_threshold: + name: Gate 3 Move Threshold + still_threshold: + name: Gate 3 Still Threshold + gate_4: + move_threshold: + name: Gate 4 Move Threshold + still_threshold: + name: Gate 4 Still Threshold + gate_5: + move_threshold: + name: Gate 5 Move Threshold + still_threshold: + name: Gate 5 Still Threshold + gate_6: + move_threshold: + name: Gate 6 Move Threshold + still_threshold: + name: Gate 6 Still Threshold + gate_7: + move_threshold: + name: Gate 7 Move Threshold + still_threshold: + name: Gate 7 Still Threshold + gate_8: + move_threshold: + name: Gate 8 Move Threshold + still_threshold: + name: Gate 8 Still Threshold + gate_9: + move_threshold: + name: Gate 9 Move Threshold + still_threshold: + name: Gate 9 Still Threshold + gate_10: + move_threshold: + name: Gate 10 Move Threshold + still_threshold: + name: Gate 10 Still Threshold + gate_11: + move_threshold: + name: Gate 11 Move Threshold + still_threshold: + name: Gate 11 Still Threshold + gate_12: + move_threshold: + name: Gate 12 Move Threshold + still_threshold: + name: Gate 12 Still Threshold + gate_13: + move_threshold: + name: Gate 13 Move Threshold + still_threshold: + name: Gate 13 Still Threshold + gate_14: + move_threshold: + name: Gate 14 Move Threshold + still_threshold: + name: Gate 14 Still Threshold + gate_15: + move_threshold: + name: Gate 15 Move Threshold + still_threshold: + name: Gate 15 Still Threshold + +select: + - platform: ld2420 + operating_mode: + name: Operating Mode + +sensor: + - platform: ld2420 + moving_distance: + name: "Moving distance (cm)" + +text_sensor: + - platform: ld2420 + fw_version: + name: LD2420 Firmware diff --git a/tests/components/ld2420/test.esp32.yaml b/tests/components/ld2420/test.esp32.yaml new file mode 100644 index 0000000000..8c883664ec --- /dev/null +++ b/tests/components/ld2420/test.esp32.yaml @@ -0,0 +1,132 @@ +uart: + - id: uart_ld2420 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +ld2420: + id: my_ld2420 + +binary_sensor: + - platform: ld2420 + has_target: + name: Presence + +button: + - platform: ld2420 + apply_config: + name: Apply Config + factory_reset: + name: Factory Reset + restart_module: + name: Restart Module + revert_config: + name: Undo Edits + +number: + - platform: ld2420 + presence_timeout: + name: Detection Presence Timeout + min_gate_distance: + name: Detection Gate Minimum + max_gate_distance: + name: Detection Gate Maximum + gate_move_sensitivity: + name: Move Calibration Sensitivity Factor + gate_still_sensitivity: + name: Still Calibration Sensitivity Factor + gate_0: + move_threshold: + name: Gate 0 Move Threshold + still_threshold: + name: Gate 0 Still Threshold + gate_1: + move_threshold: + name: Gate 1 Move Threshold + still_threshold: + name: Gate 1 Still Threshold + gate_2: + move_threshold: + name: Gate 2 Move Threshold + still_threshold: + name: Gate 2 Still Threshold + gate_3: + move_threshold: + name: Gate 3 Move Threshold + still_threshold: + name: Gate 3 Still Threshold + gate_4: + move_threshold: + name: Gate 4 Move Threshold + still_threshold: + name: Gate 4 Still Threshold + gate_5: + move_threshold: + name: Gate 5 Move Threshold + still_threshold: + name: Gate 5 Still Threshold + gate_6: + move_threshold: + name: Gate 6 Move Threshold + still_threshold: + name: Gate 6 Still Threshold + gate_7: + move_threshold: + name: Gate 7 Move Threshold + still_threshold: + name: Gate 7 Still Threshold + gate_8: + move_threshold: + name: Gate 8 Move Threshold + still_threshold: + name: Gate 8 Still Threshold + gate_9: + move_threshold: + name: Gate 9 Move Threshold + still_threshold: + name: Gate 9 Still Threshold + gate_10: + move_threshold: + name: Gate 10 Move Threshold + still_threshold: + name: Gate 10 Still Threshold + gate_11: + move_threshold: + name: Gate 11 Move Threshold + still_threshold: + name: Gate 11 Still Threshold + gate_12: + move_threshold: + name: Gate 12 Move Threshold + still_threshold: + name: Gate 12 Still Threshold + gate_13: + move_threshold: + name: Gate 13 Move Threshold + still_threshold: + name: Gate 13 Still Threshold + gate_14: + move_threshold: + name: Gate 14 Move Threshold + still_threshold: + name: Gate 14 Still Threshold + gate_15: + move_threshold: + name: Gate 15 Move Threshold + still_threshold: + name: Gate 15 Still Threshold + +select: + - platform: ld2420 + operating_mode: + name: Operating Mode + +sensor: + - platform: ld2420 + moving_distance: + name: "Moving distance (cm)" + +text_sensor: + - platform: ld2420 + fw_version: + name: LD2420 Firmware diff --git a/tests/components/ld2420/test.esp8266.yaml b/tests/components/ld2420/test.esp8266.yaml new file mode 100644 index 0000000000..5e0b9db1c2 --- /dev/null +++ b/tests/components/ld2420/test.esp8266.yaml @@ -0,0 +1,132 @@ +uart: + - id: uart_ld2420 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2420: + id: my_ld2420 + +binary_sensor: + - platform: ld2420 + has_target: + name: Presence + +button: + - platform: ld2420 + apply_config: + name: Apply Config + factory_reset: + name: Factory Reset + restart_module: + name: Restart Module + revert_config: + name: Undo Edits + +number: + - platform: ld2420 + presence_timeout: + name: Detection Presence Timeout + min_gate_distance: + name: Detection Gate Minimum + max_gate_distance: + name: Detection Gate Maximum + gate_move_sensitivity: + name: Move Calibration Sensitivity Factor + gate_still_sensitivity: + name: Still Calibration Sensitivity Factor + gate_0: + move_threshold: + name: Gate 0 Move Threshold + still_threshold: + name: Gate 0 Still Threshold + gate_1: + move_threshold: + name: Gate 1 Move Threshold + still_threshold: + name: Gate 1 Still Threshold + gate_2: + move_threshold: + name: Gate 2 Move Threshold + still_threshold: + name: Gate 2 Still Threshold + gate_3: + move_threshold: + name: Gate 3 Move Threshold + still_threshold: + name: Gate 3 Still Threshold + gate_4: + move_threshold: + name: Gate 4 Move Threshold + still_threshold: + name: Gate 4 Still Threshold + gate_5: + move_threshold: + name: Gate 5 Move Threshold + still_threshold: + name: Gate 5 Still Threshold + gate_6: + move_threshold: + name: Gate 6 Move Threshold + still_threshold: + name: Gate 6 Still Threshold + gate_7: + move_threshold: + name: Gate 7 Move Threshold + still_threshold: + name: Gate 7 Still Threshold + gate_8: + move_threshold: + name: Gate 8 Move Threshold + still_threshold: + name: Gate 8 Still Threshold + gate_9: + move_threshold: + name: Gate 9 Move Threshold + still_threshold: + name: Gate 9 Still Threshold + gate_10: + move_threshold: + name: Gate 10 Move Threshold + still_threshold: + name: Gate 10 Still Threshold + gate_11: + move_threshold: + name: Gate 11 Move Threshold + still_threshold: + name: Gate 11 Still Threshold + gate_12: + move_threshold: + name: Gate 12 Move Threshold + still_threshold: + name: Gate 12 Still Threshold + gate_13: + move_threshold: + name: Gate 13 Move Threshold + still_threshold: + name: Gate 13 Still Threshold + gate_14: + move_threshold: + name: Gate 14 Move Threshold + still_threshold: + name: Gate 14 Still Threshold + gate_15: + move_threshold: + name: Gate 15 Move Threshold + still_threshold: + name: Gate 15 Still Threshold + +select: + - platform: ld2420 + operating_mode: + name: Operating Mode + +sensor: + - platform: ld2420 + moving_distance: + name: "Moving distance (cm)" + +text_sensor: + - platform: ld2420 + fw_version: + name: LD2420 Firmware diff --git a/tests/components/ld2420/test.rp2040.yaml b/tests/components/ld2420/test.rp2040.yaml new file mode 100644 index 0000000000..5e0b9db1c2 --- /dev/null +++ b/tests/components/ld2420/test.rp2040.yaml @@ -0,0 +1,132 @@ +uart: + - id: uart_ld2420 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2420: + id: my_ld2420 + +binary_sensor: + - platform: ld2420 + has_target: + name: Presence + +button: + - platform: ld2420 + apply_config: + name: Apply Config + factory_reset: + name: Factory Reset + restart_module: + name: Restart Module + revert_config: + name: Undo Edits + +number: + - platform: ld2420 + presence_timeout: + name: Detection Presence Timeout + min_gate_distance: + name: Detection Gate Minimum + max_gate_distance: + name: Detection Gate Maximum + gate_move_sensitivity: + name: Move Calibration Sensitivity Factor + gate_still_sensitivity: + name: Still Calibration Sensitivity Factor + gate_0: + move_threshold: + name: Gate 0 Move Threshold + still_threshold: + name: Gate 0 Still Threshold + gate_1: + move_threshold: + name: Gate 1 Move Threshold + still_threshold: + name: Gate 1 Still Threshold + gate_2: + move_threshold: + name: Gate 2 Move Threshold + still_threshold: + name: Gate 2 Still Threshold + gate_3: + move_threshold: + name: Gate 3 Move Threshold + still_threshold: + name: Gate 3 Still Threshold + gate_4: + move_threshold: + name: Gate 4 Move Threshold + still_threshold: + name: Gate 4 Still Threshold + gate_5: + move_threshold: + name: Gate 5 Move Threshold + still_threshold: + name: Gate 5 Still Threshold + gate_6: + move_threshold: + name: Gate 6 Move Threshold + still_threshold: + name: Gate 6 Still Threshold + gate_7: + move_threshold: + name: Gate 7 Move Threshold + still_threshold: + name: Gate 7 Still Threshold + gate_8: + move_threshold: + name: Gate 8 Move Threshold + still_threshold: + name: Gate 8 Still Threshold + gate_9: + move_threshold: + name: Gate 9 Move Threshold + still_threshold: + name: Gate 9 Still Threshold + gate_10: + move_threshold: + name: Gate 10 Move Threshold + still_threshold: + name: Gate 10 Still Threshold + gate_11: + move_threshold: + name: Gate 11 Move Threshold + still_threshold: + name: Gate 11 Still Threshold + gate_12: + move_threshold: + name: Gate 12 Move Threshold + still_threshold: + name: Gate 12 Still Threshold + gate_13: + move_threshold: + name: Gate 13 Move Threshold + still_threshold: + name: Gate 13 Still Threshold + gate_14: + move_threshold: + name: Gate 14 Move Threshold + still_threshold: + name: Gate 14 Still Threshold + gate_15: + move_threshold: + name: Gate 15 Move Threshold + still_threshold: + name: Gate 15 Still Threshold + +select: + - platform: ld2420 + operating_mode: + name: Operating Mode + +sensor: + - platform: ld2420 + moving_distance: + name: "Moving distance (cm)" + +text_sensor: + - platform: ld2420 + fw_version: + name: LD2420 Firmware diff --git a/tests/components/ledc/test.esp32-c3-idf.yaml b/tests/components/ledc/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..70352b4519 --- /dev/null +++ b/tests/components/ledc/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +esphome: + on_boot: + then: + - output.ledc.set_frequency: + id: test_ledc + frequency: 100Hz + +output: + - platform: ledc + id: test_ledc + pin: 4 diff --git a/tests/components/ledc/test.esp32-c3.yaml b/tests/components/ledc/test.esp32-c3.yaml new file mode 100644 index 0000000000..70352b4519 --- /dev/null +++ b/tests/components/ledc/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +esphome: + on_boot: + then: + - output.ledc.set_frequency: + id: test_ledc + frequency: 100Hz + +output: + - platform: ledc + id: test_ledc + pin: 4 diff --git a/tests/components/ledc/test.esp32-idf.yaml b/tests/components/ledc/test.esp32-idf.yaml new file mode 100644 index 0000000000..70352b4519 --- /dev/null +++ b/tests/components/ledc/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +esphome: + on_boot: + then: + - output.ledc.set_frequency: + id: test_ledc + frequency: 100Hz + +output: + - platform: ledc + id: test_ledc + pin: 4 diff --git a/tests/components/ledc/test.esp32.yaml b/tests/components/ledc/test.esp32.yaml new file mode 100644 index 0000000000..70352b4519 --- /dev/null +++ b/tests/components/ledc/test.esp32.yaml @@ -0,0 +1,11 @@ +esphome: + on_boot: + then: + - output.ledc.set_frequency: + id: test_ledc + frequency: 100Hz + +output: + - platform: ledc + id: test_ledc + pin: 4 diff --git a/tests/components/light/test.esp32-c3-idf.yaml b/tests/components/light/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..8e1709838a --- /dev/null +++ b/tests/components/light/test.esp32-c3-idf.yaml @@ -0,0 +1,132 @@ +esphome: + on_boot: + then: + - light.toggle: test_binary_light + - light.turn_off: test_rgb_light + - light.turn_on: + id: test_rgb_light + brightness: 100% + red: 100% + green: 100% + blue: 1.0 + - light.control: + id: test_monochromatic_light + state: on + - light.dim_relative: + id: test_monochromatic_light + relative_brightness: 5% + +output: + - platform: gpio + id: test_binary + pin: 0 + - platform: ledc + id: test_ledc_1 + pin: 1 + - platform: ledc + id: test_ledc_2 + pin: 2 + - platform: ledc + id: test_ledc_3 + pin: 3 + - platform: ledc + id: test_ledc_4 + pin: 4 + - platform: ledc + id: test_ledc_5 + pin: 5 + +light: + - platform: binary + id: test_binary_light + name: Binary Light + output: test_binary + effects: + - strobe: + on_state: + - logger.log: Binary light state changed + - platform: monochromatic + id: test_monochromatic_light + name: Monochromatic Light + output: test_ledc_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% + - platform: rgb + id: test_rgb_light + name: RGB Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + - platform: rgbw + id: test_rgbw_light + name: RGBW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + white: test_ledc_4 + color_interlock: true + - platform: rgbww + id: test_rgbww_light + name: RGBWW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + cold_white: test_ledc_4 + warm_white: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: rgbct + id: test_rgbct_light + name: RGBCT Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + color_temperature: test_ledc_4 + white_brightness: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: cwww + id: test_cwww_light + name: CWWW Light + cold_white: test_ledc_1 + warm_white: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true + - platform: color_temperature + id: test_color_temperature_light + name: CT Light + color_temperature: test_ledc_1 + brightness: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/light/test.esp32-c3.yaml b/tests/components/light/test.esp32-c3.yaml new file mode 100644 index 0000000000..8e1709838a --- /dev/null +++ b/tests/components/light/test.esp32-c3.yaml @@ -0,0 +1,132 @@ +esphome: + on_boot: + then: + - light.toggle: test_binary_light + - light.turn_off: test_rgb_light + - light.turn_on: + id: test_rgb_light + brightness: 100% + red: 100% + green: 100% + blue: 1.0 + - light.control: + id: test_monochromatic_light + state: on + - light.dim_relative: + id: test_monochromatic_light + relative_brightness: 5% + +output: + - platform: gpio + id: test_binary + pin: 0 + - platform: ledc + id: test_ledc_1 + pin: 1 + - platform: ledc + id: test_ledc_2 + pin: 2 + - platform: ledc + id: test_ledc_3 + pin: 3 + - platform: ledc + id: test_ledc_4 + pin: 4 + - platform: ledc + id: test_ledc_5 + pin: 5 + +light: + - platform: binary + id: test_binary_light + name: Binary Light + output: test_binary + effects: + - strobe: + on_state: + - logger.log: Binary light state changed + - platform: monochromatic + id: test_monochromatic_light + name: Monochromatic Light + output: test_ledc_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% + - platform: rgb + id: test_rgb_light + name: RGB Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + - platform: rgbw + id: test_rgbw_light + name: RGBW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + white: test_ledc_4 + color_interlock: true + - platform: rgbww + id: test_rgbww_light + name: RGBWW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + cold_white: test_ledc_4 + warm_white: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: rgbct + id: test_rgbct_light + name: RGBCT Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + color_temperature: test_ledc_4 + white_brightness: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: cwww + id: test_cwww_light + name: CWWW Light + cold_white: test_ledc_1 + warm_white: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true + - platform: color_temperature + id: test_color_temperature_light + name: CT Light + color_temperature: test_ledc_1 + brightness: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/light/test.esp32-idf.yaml b/tests/components/light/test.esp32-idf.yaml new file mode 100644 index 0000000000..7e5718d8d4 --- /dev/null +++ b/tests/components/light/test.esp32-idf.yaml @@ -0,0 +1,132 @@ +esphome: + on_boot: + then: + - light.toggle: test_binary_light + - light.turn_off: test_rgb_light + - light.turn_on: + id: test_rgb_light + brightness: 100% + red: 100% + green: 100% + blue: 1.0 + - light.control: + id: test_monochromatic_light + state: on + - light.dim_relative: + id: test_monochromatic_light + relative_brightness: 5% + +output: + - platform: gpio + id: test_binary + pin: 12 + - platform: ledc + id: test_ledc_1 + pin: 13 + - platform: ledc + id: test_ledc_2 + pin: 14 + - platform: ledc + id: test_ledc_3 + pin: 15 + - platform: ledc + id: test_ledc_4 + pin: 16 + - platform: ledc + id: test_ledc_5 + pin: 17 + +light: + - platform: binary + id: test_binary_light + name: Binary Light + output: test_binary + effects: + - strobe: + on_state: + - logger.log: Binary light state changed + - platform: monochromatic + id: test_monochromatic_light + name: Monochromatic Light + output: test_ledc_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% + - platform: rgb + id: test_rgb_light + name: RGB Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + - platform: rgbw + id: test_rgbw_light + name: RGBW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + white: test_ledc_4 + color_interlock: true + - platform: rgbww + id: test_rgbww_light + name: RGBWW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + cold_white: test_ledc_4 + warm_white: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: rgbct + id: test_rgbct_light + name: RGBCT Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + color_temperature: test_ledc_4 + white_brightness: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: cwww + id: test_cwww_light + name: CWWW Light + cold_white: test_ledc_1 + warm_white: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true + - platform: color_temperature + id: test_color_temperature_light + name: CT Light + color_temperature: test_ledc_1 + brightness: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/light/test.esp32.yaml b/tests/components/light/test.esp32.yaml new file mode 100644 index 0000000000..7e5718d8d4 --- /dev/null +++ b/tests/components/light/test.esp32.yaml @@ -0,0 +1,132 @@ +esphome: + on_boot: + then: + - light.toggle: test_binary_light + - light.turn_off: test_rgb_light + - light.turn_on: + id: test_rgb_light + brightness: 100% + red: 100% + green: 100% + blue: 1.0 + - light.control: + id: test_monochromatic_light + state: on + - light.dim_relative: + id: test_monochromatic_light + relative_brightness: 5% + +output: + - platform: gpio + id: test_binary + pin: 12 + - platform: ledc + id: test_ledc_1 + pin: 13 + - platform: ledc + id: test_ledc_2 + pin: 14 + - platform: ledc + id: test_ledc_3 + pin: 15 + - platform: ledc + id: test_ledc_4 + pin: 16 + - platform: ledc + id: test_ledc_5 + pin: 17 + +light: + - platform: binary + id: test_binary_light + name: Binary Light + output: test_binary + effects: + - strobe: + on_state: + - logger.log: Binary light state changed + - platform: monochromatic + id: test_monochromatic_light + name: Monochromatic Light + output: test_ledc_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% + - platform: rgb + id: test_rgb_light + name: RGB Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + - platform: rgbw + id: test_rgbw_light + name: RGBW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + white: test_ledc_4 + color_interlock: true + - platform: rgbww + id: test_rgbww_light + name: RGBWW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + cold_white: test_ledc_4 + warm_white: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: rgbct + id: test_rgbct_light + name: RGBCT Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + color_temperature: test_ledc_4 + white_brightness: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: cwww + id: test_cwww_light + name: CWWW Light + cold_white: test_ledc_1 + warm_white: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true + - platform: color_temperature + id: test_color_temperature_light + name: CT Light + color_temperature: test_ledc_1 + brightness: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/light/test.esp8266.yaml b/tests/components/light/test.esp8266.yaml new file mode 100644 index 0000000000..4611fb374a --- /dev/null +++ b/tests/components/light/test.esp8266.yaml @@ -0,0 +1,132 @@ +esphome: + on_boot: + then: + - light.toggle: test_binary_light + - light.turn_off: test_rgb_light + - light.turn_on: + id: test_rgb_light + brightness: 100% + red: 100% + green: 100% + blue: 1.0 + - light.control: + id: test_monochromatic_light + state: on + - light.dim_relative: + id: test_monochromatic_light + relative_brightness: 5% + +output: + - platform: gpio + id: test_binary + pin: 4 + - platform: esp8266_pwm + id: test_ledc_1 + pin: 12 + - platform: esp8266_pwm + id: test_ledc_2 + pin: 13 + - platform: esp8266_pwm + id: test_ledc_3 + pin: 14 + - platform: esp8266_pwm + id: test_ledc_4 + pin: 15 + - platform: esp8266_pwm + id: test_ledc_5 + pin: 16 + +light: + - platform: binary + id: test_binary_light + name: Binary Light + output: test_binary + effects: + - strobe: + on_state: + - logger.log: Binary light state changed + - platform: monochromatic + id: test_monochromatic_light + name: Monochromatic Light + output: test_ledc_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% + - platform: rgb + id: test_rgb_light + name: RGB Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + - platform: rgbw + id: test_rgbw_light + name: RGBW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + white: test_ledc_4 + color_interlock: true + - platform: rgbww + id: test_rgbww_light + name: RGBWW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + cold_white: test_ledc_4 + warm_white: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: rgbct + id: test_rgbct_light + name: RGBCT Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + color_temperature: test_ledc_4 + white_brightness: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: cwww + id: test_cwww_light + name: CWWW Light + cold_white: test_ledc_1 + warm_white: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true + - platform: color_temperature + id: test_color_temperature_light + name: CT Light + color_temperature: test_ledc_1 + brightness: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/light/test.rp2040.yaml b/tests/components/light/test.rp2040.yaml new file mode 100644 index 0000000000..0215a17e71 --- /dev/null +++ b/tests/components/light/test.rp2040.yaml @@ -0,0 +1,132 @@ +esphome: + on_boot: + then: + - light.toggle: test_binary_light + - light.turn_off: test_rgb_light + - light.turn_on: + id: test_rgb_light + brightness: 100% + red: 100% + green: 100% + blue: 1.0 + - light.control: + id: test_monochromatic_light + state: on + - light.dim_relative: + id: test_monochromatic_light + relative_brightness: 5% + +output: + - platform: gpio + id: test_binary + pin: 0 + - platform: rp2040_pwm + id: test_ledc_1 + pin: 1 + - platform: rp2040_pwm + id: test_ledc_2 + pin: 2 + - platform: rp2040_pwm + id: test_ledc_3 + pin: 3 + - platform: rp2040_pwm + id: test_ledc_4 + pin: 4 + - platform: rp2040_pwm + id: test_ledc_5 + pin: 5 + +light: + - platform: binary + id: test_binary_light + name: Binary Light + output: test_binary + effects: + - strobe: + on_state: + - logger.log: Binary light state changed + - platform: monochromatic + id: test_monochromatic_light + name: Monochromatic Light + output: test_ledc_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% + - platform: rgb + id: test_rgb_light + name: RGB Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + - platform: rgbw + id: test_rgbw_light + name: RGBW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + white: test_ledc_4 + color_interlock: true + - platform: rgbww + id: test_rgbww_light + name: RGBWW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + cold_white: test_ledc_4 + warm_white: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: rgbct + id: test_rgbct_light + name: RGBCT Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + color_temperature: test_ledc_4 + white_brightness: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: cwww + id: test_cwww_light + name: CWWW Light + cold_white: test_ledc_1 + warm_white: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true + - platform: color_temperature + id: test_color_temperature_light + name: CT Light + color_temperature: test_ledc_1 + brightness: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/lightwaverf/test.esp8266.yaml b/tests/components/lightwaverf/test.esp8266.yaml new file mode 100644 index 0000000000..7ed8000271 --- /dev/null +++ b/tests/components/lightwaverf/test.esp8266.yaml @@ -0,0 +1,13 @@ +lightwaverf: + read_pin: 5 + write_pin: 4 + +button: + - platform: template + name: "Turn off sofa" + id: light_off_ceiling_sofa + on_press: + lightwaverf.send_raw: + code: [0x04, 0x00, 0x00, 0x00, 0x0f, 0x03, 0x0d, 0x09, 0x08, 0x08] + name: "Sofa" + repeat: 1 diff --git a/tests/components/lilygo_t5_47/test.esp32-c3-idf.yaml b/tests/components/lilygo_t5_47/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..41e81103ac --- /dev/null +++ b/tests/components/lilygo_t5_47/test.esp32-c3-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_lilygo_t5_47 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: 6 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/lilygo_t5_47/test.esp32-c3.yaml b/tests/components/lilygo_t5_47/test.esp32-c3.yaml new file mode 100644 index 0000000000..41e81103ac --- /dev/null +++ b/tests/components/lilygo_t5_47/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_lilygo_t5_47 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: 6 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/lilygo_t5_47/test.esp32-idf.yaml b/tests/components/lilygo_t5_47/test.esp32-idf.yaml new file mode 100644 index 0000000000..35eb3df022 --- /dev/null +++ b/tests/components/lilygo_t5_47/test.esp32-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_lilygo_t5_47 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: 14 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/lilygo_t5_47/test.esp32.yaml b/tests/components/lilygo_t5_47/test.esp32.yaml new file mode 100644 index 0000000000..35eb3df022 --- /dev/null +++ b/tests/components/lilygo_t5_47/test.esp32.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_lilygo_t5_47 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: 14 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/lilygo_t5_47/test.esp8266.yaml b/tests/components/lilygo_t5_47/test.esp8266.yaml new file mode 100644 index 0000000000..766c492b12 --- /dev/null +++ b/tests/components/lilygo_t5_47/test.esp8266.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_lilygo_t5_47 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: 12 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/lilygo_t5_47/test.rp2040.yaml b/tests/components/lilygo_t5_47/test.rp2040.yaml new file mode 100644 index 0000000000..41e81103ac --- /dev/null +++ b/tests/components/lilygo_t5_47/test.rp2040.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_lilygo_t5_47 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: 6 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/lock/test.esp32-c3-idf.yaml b/tests/components/lock/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..82297a3da4 --- /dev/null +++ b/tests/components/lock/test.esp32-c3-idf.yaml @@ -0,0 +1,36 @@ +esphome: + on_boot: + then: + - lock.lock: test_lock1 + - lock.unlock: test_lock1 + - lock.open: test_lock1 + +output: + - platform: gpio + id: test_binary + pin: 4 + +lock: + - platform: template + id: test_lock1 + name: Template Lock + lambda: |- + if (millis() > 10000) { + return LOCK_STATE_LOCKED; + } else { + return LOCK_STATE_UNLOCKED; + } + optimistic: true + assumed_state: false + on_unlock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_UNLOCKED;" + on_lock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_LOCKED;" + - platform: output + name: Generic Output Lock + id: test_lock2 + output: test_binary diff --git a/tests/components/lock/test.esp32-c3.yaml b/tests/components/lock/test.esp32-c3.yaml new file mode 100644 index 0000000000..82297a3da4 --- /dev/null +++ b/tests/components/lock/test.esp32-c3.yaml @@ -0,0 +1,36 @@ +esphome: + on_boot: + then: + - lock.lock: test_lock1 + - lock.unlock: test_lock1 + - lock.open: test_lock1 + +output: + - platform: gpio + id: test_binary + pin: 4 + +lock: + - platform: template + id: test_lock1 + name: Template Lock + lambda: |- + if (millis() > 10000) { + return LOCK_STATE_LOCKED; + } else { + return LOCK_STATE_UNLOCKED; + } + optimistic: true + assumed_state: false + on_unlock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_UNLOCKED;" + on_lock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_LOCKED;" + - platform: output + name: Generic Output Lock + id: test_lock2 + output: test_binary diff --git a/tests/components/lock/test.esp32-idf.yaml b/tests/components/lock/test.esp32-idf.yaml new file mode 100644 index 0000000000..82297a3da4 --- /dev/null +++ b/tests/components/lock/test.esp32-idf.yaml @@ -0,0 +1,36 @@ +esphome: + on_boot: + then: + - lock.lock: test_lock1 + - lock.unlock: test_lock1 + - lock.open: test_lock1 + +output: + - platform: gpio + id: test_binary + pin: 4 + +lock: + - platform: template + id: test_lock1 + name: Template Lock + lambda: |- + if (millis() > 10000) { + return LOCK_STATE_LOCKED; + } else { + return LOCK_STATE_UNLOCKED; + } + optimistic: true + assumed_state: false + on_unlock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_UNLOCKED;" + on_lock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_LOCKED;" + - platform: output + name: Generic Output Lock + id: test_lock2 + output: test_binary diff --git a/tests/components/lock/test.esp32.yaml b/tests/components/lock/test.esp32.yaml new file mode 100644 index 0000000000..82297a3da4 --- /dev/null +++ b/tests/components/lock/test.esp32.yaml @@ -0,0 +1,36 @@ +esphome: + on_boot: + then: + - lock.lock: test_lock1 + - lock.unlock: test_lock1 + - lock.open: test_lock1 + +output: + - platform: gpio + id: test_binary + pin: 4 + +lock: + - platform: template + id: test_lock1 + name: Template Lock + lambda: |- + if (millis() > 10000) { + return LOCK_STATE_LOCKED; + } else { + return LOCK_STATE_UNLOCKED; + } + optimistic: true + assumed_state: false + on_unlock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_UNLOCKED;" + on_lock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_LOCKED;" + - platform: output + name: Generic Output Lock + id: test_lock2 + output: test_binary diff --git a/tests/components/lock/test.esp8266.yaml b/tests/components/lock/test.esp8266.yaml new file mode 100644 index 0000000000..82297a3da4 --- /dev/null +++ b/tests/components/lock/test.esp8266.yaml @@ -0,0 +1,36 @@ +esphome: + on_boot: + then: + - lock.lock: test_lock1 + - lock.unlock: test_lock1 + - lock.open: test_lock1 + +output: + - platform: gpio + id: test_binary + pin: 4 + +lock: + - platform: template + id: test_lock1 + name: Template Lock + lambda: |- + if (millis() > 10000) { + return LOCK_STATE_LOCKED; + } else { + return LOCK_STATE_UNLOCKED; + } + optimistic: true + assumed_state: false + on_unlock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_UNLOCKED;" + on_lock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_LOCKED;" + - platform: output + name: Generic Output Lock + id: test_lock2 + output: test_binary diff --git a/tests/components/lock/test.rp2040.yaml b/tests/components/lock/test.rp2040.yaml new file mode 100644 index 0000000000..82297a3da4 --- /dev/null +++ b/tests/components/lock/test.rp2040.yaml @@ -0,0 +1,36 @@ +esphome: + on_boot: + then: + - lock.lock: test_lock1 + - lock.unlock: test_lock1 + - lock.open: test_lock1 + +output: + - platform: gpio + id: test_binary + pin: 4 + +lock: + - platform: template + id: test_lock1 + name: Template Lock + lambda: |- + if (millis() > 10000) { + return LOCK_STATE_LOCKED; + } else { + return LOCK_STATE_UNLOCKED; + } + optimistic: true + assumed_state: false + on_unlock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_UNLOCKED;" + on_lock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_LOCKED;" + - platform: output + name: Generic Output Lock + id: test_lock2 + output: test_binary diff --git a/tests/components/logger/test.esp32-c3-idf.yaml b/tests/components/logger/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..70b485daac --- /dev/null +++ b/tests/components/logger/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +esphome: + on_boot: + then: + - logger.log: Hello world + +logger: + level: DEBUG diff --git a/tests/components/logger/test.esp32-c3.yaml b/tests/components/logger/test.esp32-c3.yaml new file mode 100644 index 0000000000..70b485daac --- /dev/null +++ b/tests/components/logger/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +esphome: + on_boot: + then: + - logger.log: Hello world + +logger: + level: DEBUG diff --git a/tests/components/logger/test.esp32-idf.yaml b/tests/components/logger/test.esp32-idf.yaml new file mode 100644 index 0000000000..70b485daac --- /dev/null +++ b/tests/components/logger/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +esphome: + on_boot: + then: + - logger.log: Hello world + +logger: + level: DEBUG diff --git a/tests/components/logger/test.esp32.yaml b/tests/components/logger/test.esp32.yaml new file mode 100644 index 0000000000..70b485daac --- /dev/null +++ b/tests/components/logger/test.esp32.yaml @@ -0,0 +1,7 @@ +esphome: + on_boot: + then: + - logger.log: Hello world + +logger: + level: DEBUG diff --git a/tests/components/logger/test.esp8266.yaml b/tests/components/logger/test.esp8266.yaml new file mode 100644 index 0000000000..70b485daac --- /dev/null +++ b/tests/components/logger/test.esp8266.yaml @@ -0,0 +1,7 @@ +esphome: + on_boot: + then: + - logger.log: Hello world + +logger: + level: DEBUG diff --git a/tests/components/logger/test.rp2040.yaml b/tests/components/logger/test.rp2040.yaml new file mode 100644 index 0000000000..70b485daac --- /dev/null +++ b/tests/components/logger/test.rp2040.yaml @@ -0,0 +1,7 @@ +esphome: + on_boot: + then: + - logger.log: Hello world + +logger: + level: DEBUG diff --git a/tests/components/ltr390/test.esp32-c3-idf.yaml b/tests/components/ltr390/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..fee0f37ce1 --- /dev/null +++ b/tests/components/ltr390/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ltr390 + scl: 5 + sda: 4 + +sensor: + - platform: ltr390 + uv: + name: LTR390 UV + uv_index: + name: LTR390 UVI + light: + name: LTR390 Light + ambient_light: + name: LTR390 ALS + gain: X3 + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s diff --git a/tests/components/ltr390/test.esp32-c3.yaml b/tests/components/ltr390/test.esp32-c3.yaml new file mode 100644 index 0000000000..fee0f37ce1 --- /dev/null +++ b/tests/components/ltr390/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ltr390 + scl: 5 + sda: 4 + +sensor: + - platform: ltr390 + uv: + name: LTR390 UV + uv_index: + name: LTR390 UVI + light: + name: LTR390 Light + ambient_light: + name: LTR390 ALS + gain: X3 + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s diff --git a/tests/components/ltr390/test.esp32-idf.yaml b/tests/components/ltr390/test.esp32-idf.yaml new file mode 100644 index 0000000000..9786c7dac3 --- /dev/null +++ b/tests/components/ltr390/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ltr390 + scl: 16 + sda: 17 + +sensor: + - platform: ltr390 + uv: + name: LTR390 UV + uv_index: + name: LTR390 UVI + light: + name: LTR390 Light + ambient_light: + name: LTR390 ALS + gain: X3 + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s diff --git a/tests/components/ltr390/test.esp32.yaml b/tests/components/ltr390/test.esp32.yaml new file mode 100644 index 0000000000..9786c7dac3 --- /dev/null +++ b/tests/components/ltr390/test.esp32.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ltr390 + scl: 16 + sda: 17 + +sensor: + - platform: ltr390 + uv: + name: LTR390 UV + uv_index: + name: LTR390 UVI + light: + name: LTR390 Light + ambient_light: + name: LTR390 ALS + gain: X3 + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s diff --git a/tests/components/ltr390/test.esp8266.yaml b/tests/components/ltr390/test.esp8266.yaml new file mode 100644 index 0000000000..fee0f37ce1 --- /dev/null +++ b/tests/components/ltr390/test.esp8266.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ltr390 + scl: 5 + sda: 4 + +sensor: + - platform: ltr390 + uv: + name: LTR390 UV + uv_index: + name: LTR390 UVI + light: + name: LTR390 Light + ambient_light: + name: LTR390 ALS + gain: X3 + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s diff --git a/tests/components/ltr390/test.rp2040.yaml b/tests/components/ltr390/test.rp2040.yaml new file mode 100644 index 0000000000..fee0f37ce1 --- /dev/null +++ b/tests/components/ltr390/test.rp2040.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ltr390 + scl: 5 + sda: 4 + +sensor: + - platform: ltr390 + uv: + name: LTR390 UV + uv_index: + name: LTR390 UVI + light: + name: LTR390 Light + ambient_light: + name: LTR390 ALS + gain: X3 + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s diff --git a/tests/components/micro_wake_word/test.esp32-s3-idf.yaml b/tests/components/micro_wake_word/test.esp32-s3-idf.yaml new file mode 100644 index 0000000000..c0f3593cc6 --- /dev/null +++ b/tests/components/micro_wake_word/test.esp32-s3-idf.yaml @@ -0,0 +1,15 @@ +i2s_audio: + i2s_lrclk_pin: GPIO18 + i2s_bclk_pin: GPIO19 + +microphone: + - platform: i2s_audio + id: echo_microphone + i2s_din_pin: GPIO17 + adc_type: external + pdm: true + +micro_wake_word: + model: hey_jarvis + on_wake_word_detected: + - logger.log: "Wake word detected" diff --git a/tests/components/mopeka_std_check/test.esp32.yaml b/tests/components/mopeka_std_check/test.esp32.yaml new file mode 100644 index 0000000000..383e2e2a19 --- /dev/null +++ b/tests/components/mopeka_std_check/test.esp32.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + # Example using 11kg 100% propane tank. + - platform: mopeka_std_check + mac_address: D3:75:F2:DC:16:91 + tank_type: Europe_11kg + temperature: + name: "Propane test temp" + level: + name: "Propane test level" + distance: + name: "Propane test distance" + battery_level: + name: "Propane test battery level" diff --git a/tests/components/neopixelbus/test.esp32-c3.yaml b/tests/components/neopixelbus/test.esp32-c3.yaml new file mode 100644 index 0000000000..f2b53ab1e9 --- /dev/null +++ b/tests/components/neopixelbus/test.esp32-c3.yaml @@ -0,0 +1,19 @@ +light: + - platform: neopixelbus + id: addr3 + name: Neopixelbus Light + gamma_correct: 2.8 + color_correct: [0.0, 0.0, 0.0, 0.0] + default_transition_length: 10s + effects: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + type: GRBW + variant: SK6812 + method: + type: esp32_rmt + channel: 0 + num_leds: 5 + pin: 4 diff --git a/tests/components/neopixelbus/test.esp32.yaml b/tests/components/neopixelbus/test.esp32.yaml new file mode 100644 index 0000000000..fd468586e0 --- /dev/null +++ b/tests/components/neopixelbus/test.esp32.yaml @@ -0,0 +1,17 @@ +light: + - platform: neopixelbus + id: addr3 + name: Neopixelbus Light + gamma_correct: 2.8 + color_correct: [0.0, 0.0, 0.0, 0.0] + default_transition_length: 10s + effects: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + type: GRBW + variant: SK6812 + method: ESP32_I2S_0 + num_leds: 5 + pin: 4 diff --git a/tests/components/neopixelbus/test.esp8266.yaml b/tests/components/neopixelbus/test.esp8266.yaml new file mode 100644 index 0000000000..2c1f16a38c --- /dev/null +++ b/tests/components/neopixelbus/test.esp8266.yaml @@ -0,0 +1,17 @@ +light: + - platform: neopixelbus + id: addr3 + name: Neopixelbus Light + gamma_correct: 2.8 + color_correct: [0.0, 0.0, 0.0, 0.0] + default_transition_length: 10s + effects: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + type: GRBW + variant: SK6812 + method: esp8266_uart + num_leds: 5 + pin: 2 diff --git a/tests/components/network/test.esp32-c3-idf.yaml b/tests/components/network/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..147afd1e81 --- /dev/null +++ b/tests/components/network/test.esp32-c3-idf.yaml @@ -0,0 +1,6 @@ +wifi: + ssid: MySSID + password: password1 + +network: + enable_ipv6: true diff --git a/tests/components/network/test.esp32-c3.yaml b/tests/components/network/test.esp32-c3.yaml new file mode 100644 index 0000000000..147afd1e81 --- /dev/null +++ b/tests/components/network/test.esp32-c3.yaml @@ -0,0 +1,6 @@ +wifi: + ssid: MySSID + password: password1 + +network: + enable_ipv6: true diff --git a/tests/components/network/test.esp32-idf.yaml b/tests/components/network/test.esp32-idf.yaml new file mode 100644 index 0000000000..147afd1e81 --- /dev/null +++ b/tests/components/network/test.esp32-idf.yaml @@ -0,0 +1,6 @@ +wifi: + ssid: MySSID + password: password1 + +network: + enable_ipv6: true diff --git a/tests/components/network/test.esp32.yaml b/tests/components/network/test.esp32.yaml new file mode 100644 index 0000000000..147afd1e81 --- /dev/null +++ b/tests/components/network/test.esp32.yaml @@ -0,0 +1,6 @@ +wifi: + ssid: MySSID + password: password1 + +network: + enable_ipv6: true diff --git a/tests/components/network/test.esp8266.yaml b/tests/components/network/test.esp8266.yaml new file mode 100644 index 0000000000..147afd1e81 --- /dev/null +++ b/tests/components/network/test.esp8266.yaml @@ -0,0 +1,6 @@ +wifi: + ssid: MySSID + password: password1 + +network: + enable_ipv6: true diff --git a/tests/components/network/test.rp2040.yaml b/tests/components/network/test.rp2040.yaml new file mode 100644 index 0000000000..147afd1e81 --- /dev/null +++ b/tests/components/network/test.rp2040.yaml @@ -0,0 +1,6 @@ +wifi: + ssid: MySSID + password: password1 + +network: + enable_ipv6: true diff --git a/tests/components/nextion/test.esp32-c3-idf.yaml b/tests/components/nextion/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..5881d6e165 --- /dev/null +++ b/tests/components/nextion/test.esp32-c3-idf.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_nextion + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + tft_url: http://esphome.io/default35.tft + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.esp32-c3.yaml b/tests/components/nextion/test.esp32-c3.yaml new file mode 100644 index 0000000000..5881d6e165 --- /dev/null +++ b/tests/components/nextion/test.esp32-c3.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_nextion + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + tft_url: http://esphome.io/default35.tft + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.esp32-idf.yaml b/tests/components/nextion/test.esp32-idf.yaml new file mode 100644 index 0000000000..27568ebc2a --- /dev/null +++ b/tests/components/nextion/test.esp32-idf.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_nextion + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + tft_url: http://esphome.io/default35.tft + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.esp32.yaml b/tests/components/nextion/test.esp32.yaml new file mode 100644 index 0000000000..27568ebc2a --- /dev/null +++ b/tests/components/nextion/test.esp32.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_nextion + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + tft_url: http://esphome.io/default35.tft + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.esp8266.yaml b/tests/components/nextion/test.esp8266.yaml new file mode 100644 index 0000000000..5881d6e165 --- /dev/null +++ b/tests/components/nextion/test.esp8266.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_nextion + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + tft_url: http://esphome.io/default35.tft + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.rp2040.yaml b/tests/components/nextion/test.rp2040.yaml new file mode 100644 index 0000000000..a1c5848ce6 --- /dev/null +++ b/tests/components/nextion/test.rp2040.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_nextion + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/noblex/test.esp32-c3-idf.yaml b/tests/components/noblex/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..f5e471a9a7 --- /dev/null +++ b/tests/components/noblex/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +remote_receiver: + id: rcvr + pin: 4 + dump: all + +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +sensor: + - platform: template + id: noblex_ac_sensor + lambda: "return 21;" + +climate: + - platform: noblex + name: AC Living + id: noblex_ac + sensor: noblex_ac_sensor + receiver_id: rcvr diff --git a/tests/components/noblex/test.esp32-c3.yaml b/tests/components/noblex/test.esp32-c3.yaml new file mode 100644 index 0000000000..f5e471a9a7 --- /dev/null +++ b/tests/components/noblex/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +remote_receiver: + id: rcvr + pin: 4 + dump: all + +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +sensor: + - platform: template + id: noblex_ac_sensor + lambda: "return 21;" + +climate: + - platform: noblex + name: AC Living + id: noblex_ac + sensor: noblex_ac_sensor + receiver_id: rcvr diff --git a/tests/components/noblex/test.esp32-idf.yaml b/tests/components/noblex/test.esp32-idf.yaml new file mode 100644 index 0000000000..f5e471a9a7 --- /dev/null +++ b/tests/components/noblex/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +remote_receiver: + id: rcvr + pin: 4 + dump: all + +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +sensor: + - platform: template + id: noblex_ac_sensor + lambda: "return 21;" + +climate: + - platform: noblex + name: AC Living + id: noblex_ac + sensor: noblex_ac_sensor + receiver_id: rcvr diff --git a/tests/components/noblex/test.esp32.yaml b/tests/components/noblex/test.esp32.yaml new file mode 100644 index 0000000000..f5e471a9a7 --- /dev/null +++ b/tests/components/noblex/test.esp32.yaml @@ -0,0 +1,20 @@ +remote_receiver: + id: rcvr + pin: 4 + dump: all + +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +sensor: + - platform: template + id: noblex_ac_sensor + lambda: "return 21;" + +climate: + - platform: noblex + name: AC Living + id: noblex_ac + sensor: noblex_ac_sensor + receiver_id: rcvr diff --git a/tests/components/noblex/test.esp8266.yaml b/tests/components/noblex/test.esp8266.yaml new file mode 100644 index 0000000000..f5e471a9a7 --- /dev/null +++ b/tests/components/noblex/test.esp8266.yaml @@ -0,0 +1,20 @@ +remote_receiver: + id: rcvr + pin: 4 + dump: all + +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +sensor: + - platform: template + id: noblex_ac_sensor + lambda: "return 21;" + +climate: + - platform: noblex + name: AC Living + id: noblex_ac + sensor: noblex_ac_sensor + receiver_id: rcvr diff --git a/tests/components/ntc/test.esp32-c3.yaml b/tests/components/ntc/test.esp32-c3.yaml new file mode 100644 index 0000000000..c0edb83d9d --- /dev/null +++ b/tests/components/ntc/test.esp32-c3.yaml @@ -0,0 +1,26 @@ +sensor: + - platform: adc + id: my_sensor + pin: 4 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/ntc/test.esp32-idf.yaml b/tests/components/ntc/test.esp32-idf.yaml new file mode 100644 index 0000000000..3e0e901b6e --- /dev/null +++ b/tests/components/ntc/test.esp32-idf.yaml @@ -0,0 +1,26 @@ +sensor: + - platform: adc + id: my_sensor + pin: 32 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/ntc/test.esp32-s2.yaml b/tests/components/ntc/test.esp32-s2.yaml new file mode 100644 index 0000000000..c0edb83d9d --- /dev/null +++ b/tests/components/ntc/test.esp32-s2.yaml @@ -0,0 +1,26 @@ +sensor: + - platform: adc + id: my_sensor + pin: 4 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/ntc/test.esp32-s3.yaml b/tests/components/ntc/test.esp32-s3.yaml new file mode 100644 index 0000000000..c0edb83d9d --- /dev/null +++ b/tests/components/ntc/test.esp32-s3.yaml @@ -0,0 +1,26 @@ +sensor: + - platform: adc + id: my_sensor + pin: 4 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/ntc/test.esp32.yaml b/tests/components/ntc/test.esp32.yaml new file mode 100644 index 0000000000..3e0e901b6e --- /dev/null +++ b/tests/components/ntc/test.esp32.yaml @@ -0,0 +1,26 @@ +sensor: + - platform: adc + id: my_sensor + pin: 32 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/ntc/test.esp8266.yaml b/tests/components/ntc/test.esp8266.yaml new file mode 100644 index 0000000000..370d16d3f7 --- /dev/null +++ b/tests/components/ntc/test.esp8266.yaml @@ -0,0 +1,25 @@ +sensor: + - platform: adc + id: my_sensor + pin: A0 + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/ntc/test.rp2040.yaml b/tests/components/ntc/test.rp2040.yaml new file mode 100644 index 0000000000..9c7ba7b539 --- /dev/null +++ b/tests/components/ntc/test.rp2040.yaml @@ -0,0 +1,25 @@ +sensor: + - platform: adc + id: my_sensor + pin: 26 + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/qmc5883l/test.esp32-c3-idf.yaml b/tests/components/qmc5883l/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..841bbd5d1e --- /dev/null +++ b/tests/components/qmc5883l/test.esp32-c3-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_qmc5883l + scl: 5 + sda: 4 + +sensor: + - platform: qmc5883l + address: 0x0D + field_strength_x: + name: QMC5883L Field Strength X + field_strength_y: + name: QMC5883L Field Strength Y + field_strength_z: + name: QMC5883L Field Strength Z + heading: + name: QMC5883L Heading + temperature: + name: QMC5883L Temperature + range: 800uT + oversampling: 256x + update_interval: 15s diff --git a/tests/components/qmc5883l/test.esp32-c3.yaml b/tests/components/qmc5883l/test.esp32-c3.yaml new file mode 100644 index 0000000000..841bbd5d1e --- /dev/null +++ b/tests/components/qmc5883l/test.esp32-c3.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_qmc5883l + scl: 5 + sda: 4 + +sensor: + - platform: qmc5883l + address: 0x0D + field_strength_x: + name: QMC5883L Field Strength X + field_strength_y: + name: QMC5883L Field Strength Y + field_strength_z: + name: QMC5883L Field Strength Z + heading: + name: QMC5883L Heading + temperature: + name: QMC5883L Temperature + range: 800uT + oversampling: 256x + update_interval: 15s diff --git a/tests/components/qmc5883l/test.esp32-idf.yaml b/tests/components/qmc5883l/test.esp32-idf.yaml new file mode 100644 index 0000000000..9acd391497 --- /dev/null +++ b/tests/components/qmc5883l/test.esp32-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_qmc5883l + scl: 16 + sda: 17 + +sensor: + - platform: qmc5883l + address: 0x0D + field_strength_x: + name: QMC5883L Field Strength X + field_strength_y: + name: QMC5883L Field Strength Y + field_strength_z: + name: QMC5883L Field Strength Z + heading: + name: QMC5883L Heading + temperature: + name: QMC5883L Temperature + range: 800uT + oversampling: 256x + update_interval: 15s diff --git a/tests/components/qmc5883l/test.esp32.yaml b/tests/components/qmc5883l/test.esp32.yaml new file mode 100644 index 0000000000..9acd391497 --- /dev/null +++ b/tests/components/qmc5883l/test.esp32.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_qmc5883l + scl: 16 + sda: 17 + +sensor: + - platform: qmc5883l + address: 0x0D + field_strength_x: + name: QMC5883L Field Strength X + field_strength_y: + name: QMC5883L Field Strength Y + field_strength_z: + name: QMC5883L Field Strength Z + heading: + name: QMC5883L Heading + temperature: + name: QMC5883L Temperature + range: 800uT + oversampling: 256x + update_interval: 15s diff --git a/tests/components/qmc5883l/test.esp8266.yaml b/tests/components/qmc5883l/test.esp8266.yaml new file mode 100644 index 0000000000..841bbd5d1e --- /dev/null +++ b/tests/components/qmc5883l/test.esp8266.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_qmc5883l + scl: 5 + sda: 4 + +sensor: + - platform: qmc5883l + address: 0x0D + field_strength_x: + name: QMC5883L Field Strength X + field_strength_y: + name: QMC5883L Field Strength Y + field_strength_z: + name: QMC5883L Field Strength Z + heading: + name: QMC5883L Heading + temperature: + name: QMC5883L Temperature + range: 800uT + oversampling: 256x + update_interval: 15s diff --git a/tests/components/qmc5883l/test.rp2040.yaml b/tests/components/qmc5883l/test.rp2040.yaml new file mode 100644 index 0000000000..841bbd5d1e --- /dev/null +++ b/tests/components/qmc5883l/test.rp2040.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_qmc5883l + scl: 5 + sda: 4 + +sensor: + - platform: qmc5883l + address: 0x0D + field_strength_x: + name: QMC5883L Field Strength X + field_strength_y: + name: QMC5883L Field Strength Y + field_strength_z: + name: QMC5883L Field Strength Z + heading: + name: QMC5883L Heading + temperature: + name: QMC5883L Temperature + range: 800uT + oversampling: 256x + update_interval: 15s diff --git a/tests/components/qmp6988/test.esp32-c3-idf.yaml b/tests/components/qmp6988/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..bcd87ae6b8 --- /dev/null +++ b/tests/components/qmp6988/test.esp32-c3-idf.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_qmp6988 + scl: 5 + sda: 4 + +sensor: + - platform: qmp6988 + temperature: + name: QMP6988 Temperature + oversampling: 32x + pressure: + name: QMP6988 Pressure + oversampling: 2x + address: 0x70 + update_interval: 30s + iir_filter: 16x diff --git a/tests/components/qmp6988/test.esp32-c3.yaml b/tests/components/qmp6988/test.esp32-c3.yaml new file mode 100644 index 0000000000..bcd87ae6b8 --- /dev/null +++ b/tests/components/qmp6988/test.esp32-c3.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_qmp6988 + scl: 5 + sda: 4 + +sensor: + - platform: qmp6988 + temperature: + name: QMP6988 Temperature + oversampling: 32x + pressure: + name: QMP6988 Pressure + oversampling: 2x + address: 0x70 + update_interval: 30s + iir_filter: 16x diff --git a/tests/components/qmp6988/test.esp32-idf.yaml b/tests/components/qmp6988/test.esp32-idf.yaml new file mode 100644 index 0000000000..f3fbf75bbe --- /dev/null +++ b/tests/components/qmp6988/test.esp32-idf.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_qmp6988 + scl: 16 + sda: 17 + +sensor: + - platform: qmp6988 + temperature: + name: QMP6988 Temperature + oversampling: 32x + pressure: + name: QMP6988 Pressure + oversampling: 2x + address: 0x70 + update_interval: 30s + iir_filter: 16x diff --git a/tests/components/qmp6988/test.esp32.yaml b/tests/components/qmp6988/test.esp32.yaml new file mode 100644 index 0000000000..f3fbf75bbe --- /dev/null +++ b/tests/components/qmp6988/test.esp32.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_qmp6988 + scl: 16 + sda: 17 + +sensor: + - platform: qmp6988 + temperature: + name: QMP6988 Temperature + oversampling: 32x + pressure: + name: QMP6988 Pressure + oversampling: 2x + address: 0x70 + update_interval: 30s + iir_filter: 16x diff --git a/tests/components/qmp6988/test.esp8266.yaml b/tests/components/qmp6988/test.esp8266.yaml new file mode 100644 index 0000000000..bcd87ae6b8 --- /dev/null +++ b/tests/components/qmp6988/test.esp8266.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_qmp6988 + scl: 5 + sda: 4 + +sensor: + - platform: qmp6988 + temperature: + name: QMP6988 Temperature + oversampling: 32x + pressure: + name: QMP6988 Pressure + oversampling: 2x + address: 0x70 + update_interval: 30s + iir_filter: 16x diff --git a/tests/components/qmp6988/test.rp2040.yaml b/tests/components/qmp6988/test.rp2040.yaml new file mode 100644 index 0000000000..bcd87ae6b8 --- /dev/null +++ b/tests/components/qmp6988/test.rp2040.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_qmp6988 + scl: 5 + sda: 4 + +sensor: + - platform: qmp6988 + temperature: + name: QMP6988 Temperature + oversampling: 32x + pressure: + name: QMP6988 Pressure + oversampling: 2x + address: 0x70 + update_interval: 30s + iir_filter: 16x diff --git a/tests/components/qr_code/test.esp32-c3-idf.yaml b/tests/components/qr_code/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..63973b1aa2 --- /dev/null +++ b/tests/components/qr_code/test.esp32-c3-idf.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_main_lcd + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + lambda: |- + // Draw a QR code in the center of the screen + auto scale = 2; + auto size = id(homepage_qr).get_size() * scale; + auto x = (it.get_width() / 2) - (size / 2); + auto y = (it.get_height() / 2) - (size / 2); + it.qr_code(x, y, id(homepage_qr), Color(255,255,255), scale); + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html diff --git a/tests/components/qr_code/test.esp32-c3.yaml b/tests/components/qr_code/test.esp32-c3.yaml new file mode 100644 index 0000000000..63973b1aa2 --- /dev/null +++ b/tests/components/qr_code/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_main_lcd + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + lambda: |- + // Draw a QR code in the center of the screen + auto scale = 2; + auto size = id(homepage_qr).get_size() * scale; + auto x = (it.get_width() / 2) - (size / 2); + auto y = (it.get_height() / 2) - (size / 2); + it.qr_code(x, y, id(homepage_qr), Color(255,255,255), scale); + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html diff --git a/tests/components/qr_code/test.esp32-idf.yaml b/tests/components/qr_code/test.esp32-idf.yaml new file mode 100644 index 0000000000..3e70d3258f --- /dev/null +++ b/tests/components/qr_code/test.esp32-idf.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 12 + dc_pin: 13 + reset_pin: 21 + lambda: |- + // Draw a QR code in the center of the screen + auto scale = 2; + auto size = id(homepage_qr).get_size() * scale; + auto x = (it.get_width() / 2) - (size / 2); + auto y = (it.get_height() / 2) - (size / 2); + it.qr_code(x, y, id(homepage_qr), Color(255,255,255), scale); + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html diff --git a/tests/components/qr_code/test.esp32.yaml b/tests/components/qr_code/test.esp32.yaml new file mode 100644 index 0000000000..3e70d3258f --- /dev/null +++ b/tests/components/qr_code/test.esp32.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 12 + dc_pin: 13 + reset_pin: 21 + lambda: |- + // Draw a QR code in the center of the screen + auto scale = 2; + auto size = id(homepage_qr).get_size() * scale; + auto x = (it.get_width() / 2) - (size / 2); + auto y = (it.get_height() / 2) - (size / 2); + it.qr_code(x, y, id(homepage_qr), Color(255,255,255), scale); + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html diff --git a/tests/components/qr_code/test.esp8266.yaml b/tests/components/qr_code/test.esp8266.yaml new file mode 100644 index 0000000000..3c304d7575 --- /dev/null +++ b/tests/components/qr_code/test.esp8266.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_main_lcd + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 5 + dc_pin: 15 + reset_pin: 16 + lambda: |- + // Draw a QR code in the center of the screen + auto scale = 2; + auto size = id(homepage_qr).get_size() * scale; + auto x = (it.get_width() / 2) - (size / 2); + auto y = (it.get_height() / 2) - (size / 2); + it.qr_code(x, y, id(homepage_qr), Color(255,255,255), scale); + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html diff --git a/tests/components/qr_code/test.rp2040.yaml b/tests/components/qr_code/test.rp2040.yaml new file mode 100644 index 0000000000..94cb772ba3 --- /dev/null +++ b/tests/components/qr_code/test.rp2040.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_main_lcd + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 20 + dc_pin: 21 + reset_pin: 22 + lambda: |- + // Draw a QR code in the center of the screen + auto scale = 2; + auto size = id(homepage_qr).get_size() * scale; + auto x = (it.get_width() / 2) - (size / 2); + auto y = (it.get_height() / 2) - (size / 2); + it.qr_code(x, y, id(homepage_qr), Color(255,255,255), scale); + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html diff --git a/tests/components/qspi_amoled/test.esp32-s3-idf.yaml b/tests/components/qspi_amoled/test.esp32-s3-idf.yaml new file mode 100644 index 0000000000..01d1a63bcb --- /dev/null +++ b/tests/components/qspi_amoled/test.esp32-s3-idf.yaml @@ -0,0 +1,36 @@ +spi: + id: quad_spi + clk_pin: 15 + type: quad + data_pins: [14, 10, 16, 12] + +display: + - platform: qspi_amoled + model: RM690B0 + data_rate: 80MHz + spi_mode: mode0 + dimensions: + width: 450 + height: 600 + offset_width: 16 + color_order: rgb + invert_colors: false + brightness: 255 + cs_pin: 11 + reset_pin: 13 + enable_pin: 9 + + - platform: qspi_amoled + model: RM67162 + id: main_lcd + dimensions: + height: 240 + width: 536 + transform: + mirror_x: true + swap_xy: true + color_order: rgb + brightness: 255 + cs_pin: 6 + reset_pin: 17 + enable_pin: 38 diff --git a/tests/components/qwiic_pir/test.esp32-c3-idf.yaml b/tests/components/qwiic_pir/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ad52ac91c5 --- /dev/null +++ b/tests/components/qwiic_pir/test.esp32-c3-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_qwiic_pir + scl: 5 + sda: 4 + +binary_sensor: + - platform: qwiic_pir + name: Qwiic PIR Motion Sensor diff --git a/tests/components/qwiic_pir/test.esp32-c3.yaml b/tests/components/qwiic_pir/test.esp32-c3.yaml new file mode 100644 index 0000000000..ad52ac91c5 --- /dev/null +++ b/tests/components/qwiic_pir/test.esp32-c3.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_qwiic_pir + scl: 5 + sda: 4 + +binary_sensor: + - platform: qwiic_pir + name: Qwiic PIR Motion Sensor diff --git a/tests/components/qwiic_pir/test.esp32-idf.yaml b/tests/components/qwiic_pir/test.esp32-idf.yaml new file mode 100644 index 0000000000..da2e275cf3 --- /dev/null +++ b/tests/components/qwiic_pir/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_qwiic_pir + scl: 16 + sda: 17 + +binary_sensor: + - platform: qwiic_pir + name: Qwiic PIR Motion Sensor diff --git a/tests/components/qwiic_pir/test.esp32.yaml b/tests/components/qwiic_pir/test.esp32.yaml new file mode 100644 index 0000000000..da2e275cf3 --- /dev/null +++ b/tests/components/qwiic_pir/test.esp32.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_qwiic_pir + scl: 16 + sda: 17 + +binary_sensor: + - platform: qwiic_pir + name: Qwiic PIR Motion Sensor diff --git a/tests/components/qwiic_pir/test.esp8266.yaml b/tests/components/qwiic_pir/test.esp8266.yaml new file mode 100644 index 0000000000..ad52ac91c5 --- /dev/null +++ b/tests/components/qwiic_pir/test.esp8266.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_qwiic_pir + scl: 5 + sda: 4 + +binary_sensor: + - platform: qwiic_pir + name: Qwiic PIR Motion Sensor diff --git a/tests/components/qwiic_pir/test.rp2040.yaml b/tests/components/qwiic_pir/test.rp2040.yaml new file mode 100644 index 0000000000..ad52ac91c5 --- /dev/null +++ b/tests/components/qwiic_pir/test.rp2040.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_qwiic_pir + scl: 5 + sda: 4 + +binary_sensor: + - platform: qwiic_pir + name: Qwiic PIR Motion Sensor diff --git a/tests/components/rpi_dpi_rgb/test.esp32-s3-idf.yaml b/tests/components/rpi_dpi_rgb/test.esp32-s3-idf.yaml new file mode 100644 index 0000000000..9ce2d9b9fd --- /dev/null +++ b/tests/components/rpi_dpi_rgb/test.esp32-s3-idf.yaml @@ -0,0 +1,40 @@ +psram: + mode: octal + speed: 80MHz +display: + - platform: rpi_dpi_rgb + update_interval: never + auto_clear_enabled: false + id: rpi_display + color_order: RGB + rotation: 90 + dimensions: + width: 800 + height: 480 + de_pin: + number: 40 + hsync_pin: 39 + vsync_pin: 41 + pclk_pin: 42 + data_pins: + red: + - number: 45 # r1 + ignore_strapping_warning: true + - 48 # r2 + - 47 # r3 + - 21 # r4 + - number: 14 # r5 + ignore_strapping_warning: false + green: + - 5 # g0 + - 6 # g1 + - 7 # g2 + - 15 # g3 + - 16 # g4 + - 4 # g5 + blue: + - 8 # b1 + - 3 # b2 + - 46 # b3 + - 9 # b4 + - 1 # b5 diff --git a/tests/components/seeed_mr24hpc1/common.yaml b/tests/components/seeed_mr24hpc1/common.yaml new file mode 100644 index 0000000000..38692b3e5e --- /dev/null +++ b/tests/components/seeed_mr24hpc1/common.yaml @@ -0,0 +1,92 @@ +uart: + - id: seeed_mr24hpc1_uart + tx_pin: ${uart_tx_pin} + rx_pin: ${uart_rx_pin} + baud_rate: 115200 + parity: NONE + stop_bits: 1 + +seeed_mr24hpc1: + id: my_seeed_mr24hpc1 + uart_id: seeed_mr24hpc1_uart + +sensor: + - platform: seeed_mr24hpc1 + custom_presence_of_detection: + name: "Static Distance" + movement_signs: + name: "Body Movement Parameter" + custom_motion_distance: + name: "Motion Distance" + custom_spatial_static_value: + name: "Existence Energy" + custom_spatial_motion_value: + name: "Motion Energy" + custom_motion_speed: + name: "Motion Speed" + custom_mode_num: + name: "Current Custom Mode" + +binary_sensor: + - platform: seeed_mr24hpc1 + has_target: + name: "Presence Information" + +switch: + - platform: seeed_mr24hpc1 + underlying_open_function: + name: Underlying Open Function Info Output Switch + +text_sensor: + - platform: seeed_mr24hpc1 + heart_beat: + name: "Heartbeat" + product_model: + name: "Product Model" + product_id: + name: "Product ID" + hardware_model: + name: "Hardware Model" + hardware_version: + name: "Hardware Version" + keep_away: + name: "Active Reporting Of Proximity" + motion_status: + name: "Motion Information" + custom_mode_end: + name: "Custom Mode Status" + +number: + - platform: seeed_mr24hpc1 + sensitivity: + name: "Sensitivity" + custom_mode: + name: "Custom Mode" + existence_threshold: + name: "Existence Energy Threshold" + motion_threshold: + name: "Motion Energy Threshold" + motion_trigger: + name: "Motion Trigger Time" + motion_to_rest: + name: "Motion To Rest Time" + custom_unman_time: + name: "Time For Entering No Person State (Underlying Open Function)" + +select: + - platform: seeed_mr24hpc1 + scene_mode: + name: "Scene" + unman_time: + name: "Time For Entering No Person State (Standard Function)" + existence_boundary: + name: "Existence Boundary" + motion_boundary: + name: "Motion Boundary" + +button: + - platform: seeed_mr24hpc1 + restart: + name: "Module Restart" + custom_set_end: + name: "End Of Custom Mode Settings" diff --git a/tests/components/seeed_mr24hpc1/test.esp32-c3-idf.yaml b/tests/components/seeed_mr24hpc1/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4fb884abf4 --- /dev/null +++ b/tests/components/seeed_mr24hpc1/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO5 + uart_rx_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/seeed_mr24hpc1/test.esp32-c3.yaml b/tests/components/seeed_mr24hpc1/test.esp32-c3.yaml new file mode 100644 index 0000000000..4fb884abf4 --- /dev/null +++ b/tests/components/seeed_mr24hpc1/test.esp32-c3.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO5 + uart_rx_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/spi/test.esp32-s3-idf.yaml b/tests/components/spi/test.esp32-s3-idf.yaml new file mode 100644 index 0000000000..8db934023a --- /dev/null +++ b/tests/components/spi/test.esp32-s3-idf.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_id_1 + type: single + clk_pin: + number: GPIO0 + ignore_strapping_warning: true + allow_other_uses: false + mosi_pin: GPIO6 + interface: hardware + - id: quad_spi + type: quad + clk_pin: 47 + interface: spi3 + data_pins: + - number: 40 + allow_other_uses: false + - 41 + - 42 + - 43 + - id: spi_id_3 + clk_pin: 8 + mosi_pin: 9 + interface: any + diff --git a/tests/components/st7701s/test.esp32-s3-idf.yaml b/tests/components/st7701s/test.esp32-s3-idf.yaml new file mode 100644 index 0000000000..497df8c8ce --- /dev/null +++ b/tests/components/st7701s/test.esp32-s3-idf.yaml @@ -0,0 +1,60 @@ +psram: + mode: octal + speed: 80MHz +spi: + - id: lcd_spi + clk_pin: 41 + mosi_pin: 48 + +i2c: + sda: 39 + scl: 40 + scan: false + id: bus_a + +pca9554: + - id: p_c_a + pin_count: 16 + address: 0x20 + +display: + - platform: st7701s + spi_mode: MODE3 + color_order: RGB + dimensions: + width: 480 + height: 480 + invert_colors: true + transform: + mirror_x: true + mirror_y: true + cs_pin: + pca9554: p_c_a + number: 4 + reset_pin: + pca9554: p_c_a + number: 5 + de_pin: 18 + hsync_pin: 16 + vsync_pin: 17 + pclk_pin: 21 + init_sequence: 1 + data_pins: + - number: 0 + ignore_strapping_warning: true + - 1 + - 2 + - 3 + - number: 4 + ignore_strapping_warning: false + - 5 + - 6 + - 7 + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 + - 14 + - 15 diff --git a/tests/components/sun_gtil2/test.esp32-c3-idf.yaml b/tests/components/sun_gtil2/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..6ec834db98 --- /dev/null +++ b/tests/components/sun_gtil2/test.esp32-c3-idf.yaml @@ -0,0 +1,44 @@ +uart: + - id: control_to_display + rx_pin: + number: 5 + baud_rate: 9600 + +sun_gtil2: + uart_id: control_to_display + +sensor: + - platform: sun_gtil2 + temperature: + id: gtil_temperature + name: "Heatsink Temperature" + filters: + - throttle_average: 30s + dc_voltage: + id: gtil_dc_voltage + name: "DC Voltage" + filters: + - throttle_average: 30s + ac_voltage: + id: gtil_ac_voltage + name: "AC Voltage" + filters: + - throttle_average: 30s + ac_power: + id: gtil_ac_power + name: "AC Power" + dc_power: + id: gtil_dc_power + name: "DC Power" + limiter_power: + id: gtil_limiter_power + internal: true + +text_sensor: + - platform: sun_gtil2 + state: + id: gtil_state + name: "State" + serial_number: + id: gtil_serial_number + internal: true diff --git a/tests/components/sun_gtil2/test.esp32-c3.yaml b/tests/components/sun_gtil2/test.esp32-c3.yaml new file mode 100644 index 0000000000..6ec834db98 --- /dev/null +++ b/tests/components/sun_gtil2/test.esp32-c3.yaml @@ -0,0 +1,44 @@ +uart: + - id: control_to_display + rx_pin: + number: 5 + baud_rate: 9600 + +sun_gtil2: + uart_id: control_to_display + +sensor: + - platform: sun_gtil2 + temperature: + id: gtil_temperature + name: "Heatsink Temperature" + filters: + - throttle_average: 30s + dc_voltage: + id: gtil_dc_voltage + name: "DC Voltage" + filters: + - throttle_average: 30s + ac_voltage: + id: gtil_ac_voltage + name: "AC Voltage" + filters: + - throttle_average: 30s + ac_power: + id: gtil_ac_power + name: "AC Power" + dc_power: + id: gtil_dc_power + name: "DC Power" + limiter_power: + id: gtil_limiter_power + internal: true + +text_sensor: + - platform: sun_gtil2 + state: + id: gtil_state + name: "State" + serial_number: + id: gtil_serial_number + internal: true diff --git a/tests/components/sun_gtil2/test.esp32-idf.yaml b/tests/components/sun_gtil2/test.esp32-idf.yaml new file mode 100644 index 0000000000..ed1e68e574 --- /dev/null +++ b/tests/components/sun_gtil2/test.esp32-idf.yaml @@ -0,0 +1,44 @@ +uart: + - id: control_to_display + rx_pin: + number: 16 + baud_rate: 9600 + +sun_gtil2: + uart_id: control_to_display + +sensor: + - platform: sun_gtil2 + temperature: + id: gtil_temperature + name: "Heatsink Temperature" + filters: + - throttle_average: 30s + dc_voltage: + id: gtil_dc_voltage + name: "DC Voltage" + filters: + - throttle_average: 30s + ac_voltage: + id: gtil_ac_voltage + name: "AC Voltage" + filters: + - throttle_average: 30s + ac_power: + id: gtil_ac_power + name: "AC Power" + dc_power: + id: gtil_dc_power + name: "DC Power" + limiter_power: + id: gtil_limiter_power + internal: true + +text_sensor: + - platform: sun_gtil2 + state: + id: gtil_state + name: "State" + serial_number: + id: gtil_serial_number + internal: true diff --git a/tests/components/sun_gtil2/test.esp32.yaml b/tests/components/sun_gtil2/test.esp32.yaml new file mode 100644 index 0000000000..ed1e68e574 --- /dev/null +++ b/tests/components/sun_gtil2/test.esp32.yaml @@ -0,0 +1,44 @@ +uart: + - id: control_to_display + rx_pin: + number: 16 + baud_rate: 9600 + +sun_gtil2: + uart_id: control_to_display + +sensor: + - platform: sun_gtil2 + temperature: + id: gtil_temperature + name: "Heatsink Temperature" + filters: + - throttle_average: 30s + dc_voltage: + id: gtil_dc_voltage + name: "DC Voltage" + filters: + - throttle_average: 30s + ac_voltage: + id: gtil_ac_voltage + name: "AC Voltage" + filters: + - throttle_average: 30s + ac_power: + id: gtil_ac_power + name: "AC Power" + dc_power: + id: gtil_dc_power + name: "DC Power" + limiter_power: + id: gtil_limiter_power + internal: true + +text_sensor: + - platform: sun_gtil2 + state: + id: gtil_state + name: "State" + serial_number: + id: gtil_serial_number + internal: true diff --git a/tests/components/sun_gtil2/test.esp8266.yaml b/tests/components/sun_gtil2/test.esp8266.yaml new file mode 100644 index 0000000000..6ec834db98 --- /dev/null +++ b/tests/components/sun_gtil2/test.esp8266.yaml @@ -0,0 +1,44 @@ +uart: + - id: control_to_display + rx_pin: + number: 5 + baud_rate: 9600 + +sun_gtil2: + uart_id: control_to_display + +sensor: + - platform: sun_gtil2 + temperature: + id: gtil_temperature + name: "Heatsink Temperature" + filters: + - throttle_average: 30s + dc_voltage: + id: gtil_dc_voltage + name: "DC Voltage" + filters: + - throttle_average: 30s + ac_voltage: + id: gtil_ac_voltage + name: "AC Voltage" + filters: + - throttle_average: 30s + ac_power: + id: gtil_ac_power + name: "AC Power" + dc_power: + id: gtil_dc_power + name: "DC Power" + limiter_power: + id: gtil_limiter_power + internal: true + +text_sensor: + - platform: sun_gtil2 + state: + id: gtil_state + name: "State" + serial_number: + id: gtil_serial_number + internal: true diff --git a/tests/components/sun_gtil2/test.rp2040.yaml b/tests/components/sun_gtil2/test.rp2040.yaml new file mode 100644 index 0000000000..6ec834db98 --- /dev/null +++ b/tests/components/sun_gtil2/test.rp2040.yaml @@ -0,0 +1,44 @@ +uart: + - id: control_to_display + rx_pin: + number: 5 + baud_rate: 9600 + +sun_gtil2: + uart_id: control_to_display + +sensor: + - platform: sun_gtil2 + temperature: + id: gtil_temperature + name: "Heatsink Temperature" + filters: + - throttle_average: 30s + dc_voltage: + id: gtil_dc_voltage + name: "DC Voltage" + filters: + - throttle_average: 30s + ac_voltage: + id: gtil_ac_voltage + name: "AC Voltage" + filters: + - throttle_average: 30s + ac_power: + id: gtil_ac_power + name: "AC Power" + dc_power: + id: gtil_dc_power + name: "DC Power" + limiter_power: + id: gtil_limiter_power + internal: true + +text_sensor: + - platform: sun_gtil2 + state: + id: gtil_state + name: "State" + serial_number: + id: gtil_serial_number + internal: true diff --git a/tests/components/template/test.all.yaml b/tests/components/template/test.all.yaml new file mode 100644 index 0000000000..e50ffd7f67 --- /dev/null +++ b/tests/components/template/test.all.yaml @@ -0,0 +1,155 @@ +sensor: + - platform: template + name: "Template Sensor" + id: template_sens + lambda: |- + if (id(some_binary_sensor).state) { + return 42.0; + } else { + return 0.0; + } + update_interval: 60s + +esphome: + on_boot: + - sensor.template.publish: + id: template_sens + state: 42.0 + + # Templated + - sensor.template.publish: + id: template_sens + state: !lambda "return 42.0;" + + - datetime.date.set: + id: test_date + date: + year: 2021 + month: 1 + day: 1 + - datetime.date.set: + id: test_date + date: !lambda "return {.day_of_month = 1, .month = 1, .year = 2021};" + - datetime.date.set: + id: test_date + date: "2021-01-01" + +binary_sensor: + - platform: template + id: some_binary_sensor + name: "Garage Door Open" + lambda: |- + if (id(template_sens).state > 30) { + // Garage Door is open. + return true; + } else { + // Garage Door is closed. + return false; + } + +output: + - platform: template + id: outputsplit + type: float + write_action: + - logger.log: "write_action" + +switch: + - platform: template + name: "Template Switch" + lambda: |- + if (id(some_binary_sensor).state) { + return true; + } else { + return false; + } + turn_on_action: + - logger.log: "turn_on_action" + turn_off_action: + - logger.log: "turn_off_action" + +button: + - platform: template + name: "Template Button" + on_press: + - logger.log: Button Pressed + +cover: + - platform: template + name: "Template Cover" + lambda: |- + if (id(some_binary_sensor).state) { + return COVER_OPEN; + } else { + return COVER_CLOSED; + } + open_action: + - logger.log: open_action + close_action: + - logger.log: close_action + stop_action: + - logger.log: stop_action + optimistic: true + +number: + - platform: template + name: "Template number" + optimistic: true + min_value: 0 + max_value: 100 + step: 1 + +select: + - platform: template + name: "Template select" + optimistic: true + options: + - one + - two + - three + initial_option: two + +lock: + - platform: template + name: "Template Lock" + lambda: |- + if (id(some_binary_sensor).state) { + return LOCK_STATE_LOCKED; + } else { + return LOCK_STATE_UNLOCKED; + } + lock_action: + - logger.log: lock_action + unlock_action: + - logger.log: unlock_action + open_action: + - logger.log: open_action + +text: + - platform: template + name: "Template text" + optimistic: true + min_length: 0 + max_length: 100 + mode: text + +alarm_control_panel: + - platform: template + name: Alarm Panel + codes: + - "1234" + +datetime: + - platform: template + name: Date + id: test_date + type: date + set_action: + - logger.log: "set_value" + on_value: + - logger.log: + format: "Date: %04d-%02d-%02d" + args: + - x.year + - x.month + - x.day_of_month diff --git a/tests/components/tt21100/test.esp32-s2.yaml b/tests/components/tt21100/test.esp32-s2.yaml new file mode 100644 index 0000000000..7ebabcb130 --- /dev/null +++ b/tests/components/tt21100/test.esp32-s2.yaml @@ -0,0 +1,43 @@ +i2c: + sda: GPIO8 + scl: GPIO18 + +spi: + clk_pin: 7 + mosi_pin: 11 + miso_pin: 9 + +display: + - platform: ili9xxx + id: my_display + model: ili9341 + cs_pin: 5 + dc_pin: 12 + reset_pin: 33 + auto_clear_enabled: false + data_rate: 40MHz + dimensions: 320x240 + update_interval: never + transform: + mirror_y: false + mirror_x: false + swap_xy: true + +touchscreen: + - platform: tt21100 + address: 0x24 + interrupt_pin: GPIO3 + on_touch: + - logger.log: "Touchscreen:: Touched" + +binary_sensor: + - platform: tt21100 + index: 0 + name: "Home" + + - platform: touchscreen + name: FanLo + x_min: 0 + x_max: 105 + y_min: 0 + y_max: 80 diff --git a/tests/components/uart/test.esp32-c3-idf.yaml b/tests/components/uart/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..09178f1663 --- /dev/null +++ b/tests/components/uart/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - uart.write: 'Hello World' + - uart.write: [0x00, 0x20, 0x42] + +uart: + - id: uart_uart + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + data_bits: 8 + rx_buffer_size: 512 + parity: EVEN + stop_bits: 2 diff --git a/tests/components/uart/test.esp32-c3.yaml b/tests/components/uart/test.esp32-c3.yaml new file mode 100644 index 0000000000..09178f1663 --- /dev/null +++ b/tests/components/uart/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - uart.write: 'Hello World' + - uart.write: [0x00, 0x20, 0x42] + +uart: + - id: uart_uart + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + data_bits: 8 + rx_buffer_size: 512 + parity: EVEN + stop_bits: 2 diff --git a/tests/components/uart/test.esp32-idf.yaml b/tests/components/uart/test.esp32-idf.yaml new file mode 100644 index 0000000000..bef5b460ab --- /dev/null +++ b/tests/components/uart/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - uart.write: 'Hello World' + - uart.write: [0x00, 0x20, 0x42] + +uart: + - id: uart_uart + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + data_bits: 8 + rx_buffer_size: 512 + parity: EVEN + stop_bits: 2 diff --git a/tests/components/uart/test.esp32.yaml b/tests/components/uart/test.esp32.yaml new file mode 100644 index 0000000000..bef5b460ab --- /dev/null +++ b/tests/components/uart/test.esp32.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - uart.write: 'Hello World' + - uart.write: [0x00, 0x20, 0x42] + +uart: + - id: uart_uart + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + data_bits: 8 + rx_buffer_size: 512 + parity: EVEN + stop_bits: 2 diff --git a/tests/components/uart/test.esp8266.yaml b/tests/components/uart/test.esp8266.yaml new file mode 100644 index 0000000000..09178f1663 --- /dev/null +++ b/tests/components/uart/test.esp8266.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - uart.write: 'Hello World' + - uart.write: [0x00, 0x20, 0x42] + +uart: + - id: uart_uart + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + data_bits: 8 + rx_buffer_size: 512 + parity: EVEN + stop_bits: 2 diff --git a/tests/components/uart/test.rp2040.yaml b/tests/components/uart/test.rp2040.yaml new file mode 100644 index 0000000000..09178f1663 --- /dev/null +++ b/tests/components/uart/test.rp2040.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - uart.write: 'Hello World' + - uart.write: [0x00, 0x20, 0x42] + +uart: + - id: uart_uart + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + data_bits: 8 + rx_buffer_size: 512 + parity: EVEN + stop_bits: 2 diff --git a/tests/components/ufire_ec/test.esp32-c3-idf.yaml b/tests/components/ufire_ec/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..aa72c992b8 --- /dev/null +++ b/tests/components/ufire_ec/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ec.calibrate_probe: + id: ufire_ec_board + solution: 0.146 + temperature: !lambda "return id(test_sensor).state;" + - ufire_ec.reset: + +i2c: + - id: i2c_ufire_ec + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: test_sensor + temperature_compensation: 20.0 + temperature_coefficient: 0.019 diff --git a/tests/components/ufire_ec/test.esp32-c3.yaml b/tests/components/ufire_ec/test.esp32-c3.yaml new file mode 100644 index 0000000000..aa72c992b8 --- /dev/null +++ b/tests/components/ufire_ec/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ec.calibrate_probe: + id: ufire_ec_board + solution: 0.146 + temperature: !lambda "return id(test_sensor).state;" + - ufire_ec.reset: + +i2c: + - id: i2c_ufire_ec + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: test_sensor + temperature_compensation: 20.0 + temperature_coefficient: 0.019 diff --git a/tests/components/ufire_ec/test.esp32-idf.yaml b/tests/components/ufire_ec/test.esp32-idf.yaml new file mode 100644 index 0000000000..5e6a0daa9e --- /dev/null +++ b/tests/components/ufire_ec/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ec.calibrate_probe: + id: ufire_ec_board + solution: 0.146 + temperature: !lambda "return id(test_sensor).state;" + - ufire_ec.reset: + +i2c: + - id: i2c_ufire_ec + scl: 16 + sda: 17 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: test_sensor + temperature_compensation: 20.0 + temperature_coefficient: 0.019 diff --git a/tests/components/ufire_ec/test.esp32.yaml b/tests/components/ufire_ec/test.esp32.yaml new file mode 100644 index 0000000000..5e6a0daa9e --- /dev/null +++ b/tests/components/ufire_ec/test.esp32.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ec.calibrate_probe: + id: ufire_ec_board + solution: 0.146 + temperature: !lambda "return id(test_sensor).state;" + - ufire_ec.reset: + +i2c: + - id: i2c_ufire_ec + scl: 16 + sda: 17 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: test_sensor + temperature_compensation: 20.0 + temperature_coefficient: 0.019 diff --git a/tests/components/ufire_ec/test.esp8266.yaml b/tests/components/ufire_ec/test.esp8266.yaml new file mode 100644 index 0000000000..aa72c992b8 --- /dev/null +++ b/tests/components/ufire_ec/test.esp8266.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ec.calibrate_probe: + id: ufire_ec_board + solution: 0.146 + temperature: !lambda "return id(test_sensor).state;" + - ufire_ec.reset: + +i2c: + - id: i2c_ufire_ec + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: test_sensor + temperature_compensation: 20.0 + temperature_coefficient: 0.019 diff --git a/tests/components/ufire_ec/test.rp2040.yaml b/tests/components/ufire_ec/test.rp2040.yaml new file mode 100644 index 0000000000..aa72c992b8 --- /dev/null +++ b/tests/components/ufire_ec/test.rp2040.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ec.calibrate_probe: + id: ufire_ec_board + solution: 0.146 + temperature: !lambda "return id(test_sensor).state;" + - ufire_ec.reset: + +i2c: + - id: i2c_ufire_ec + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: test_sensor + temperature_compensation: 20.0 + temperature_coefficient: 0.019 diff --git a/tests/components/ufire_ise/test.esp32-c3-idf.yaml b/tests/components/ufire_ise/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..36aec73113 --- /dev/null +++ b/tests/components/ufire_ise/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ise.calibrate_probe_high: + id: ufire_ise_sensor + solution: 7.0 + - ufire_ise.calibrate_probe_low: + id: ufire_ise_sensor + solution: 4.0 + - ufire_ise.reset: + +i2c: + - id: i2c_ufire_ise + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ise + id: ufire_ise_sensor + temperature_sensor: test_sensor + ph: + name: Ufire pH diff --git a/tests/components/ufire_ise/test.esp32-c3.yaml b/tests/components/ufire_ise/test.esp32-c3.yaml new file mode 100644 index 0000000000..36aec73113 --- /dev/null +++ b/tests/components/ufire_ise/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ise.calibrate_probe_high: + id: ufire_ise_sensor + solution: 7.0 + - ufire_ise.calibrate_probe_low: + id: ufire_ise_sensor + solution: 4.0 + - ufire_ise.reset: + +i2c: + - id: i2c_ufire_ise + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ise + id: ufire_ise_sensor + temperature_sensor: test_sensor + ph: + name: Ufire pH diff --git a/tests/components/ufire_ise/test.esp32-idf.yaml b/tests/components/ufire_ise/test.esp32-idf.yaml new file mode 100644 index 0000000000..9ed0ac433a --- /dev/null +++ b/tests/components/ufire_ise/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ise.calibrate_probe_high: + id: ufire_ise_sensor + solution: 7.0 + - ufire_ise.calibrate_probe_low: + id: ufire_ise_sensor + solution: 4.0 + - ufire_ise.reset: + +i2c: + - id: i2c_ufire_ise + scl: 16 + sda: 17 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ise + id: ufire_ise_sensor + temperature_sensor: test_sensor + ph: + name: Ufire pH diff --git a/tests/components/ufire_ise/test.esp32.yaml b/tests/components/ufire_ise/test.esp32.yaml new file mode 100644 index 0000000000..9ed0ac433a --- /dev/null +++ b/tests/components/ufire_ise/test.esp32.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ise.calibrate_probe_high: + id: ufire_ise_sensor + solution: 7.0 + - ufire_ise.calibrate_probe_low: + id: ufire_ise_sensor + solution: 4.0 + - ufire_ise.reset: + +i2c: + - id: i2c_ufire_ise + scl: 16 + sda: 17 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ise + id: ufire_ise_sensor + temperature_sensor: test_sensor + ph: + name: Ufire pH diff --git a/tests/components/ufire_ise/test.esp8266.yaml b/tests/components/ufire_ise/test.esp8266.yaml new file mode 100644 index 0000000000..36aec73113 --- /dev/null +++ b/tests/components/ufire_ise/test.esp8266.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ise.calibrate_probe_high: + id: ufire_ise_sensor + solution: 7.0 + - ufire_ise.calibrate_probe_low: + id: ufire_ise_sensor + solution: 4.0 + - ufire_ise.reset: + +i2c: + - id: i2c_ufire_ise + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ise + id: ufire_ise_sensor + temperature_sensor: test_sensor + ph: + name: Ufire pH diff --git a/tests/components/ufire_ise/test.rp2040.yaml b/tests/components/ufire_ise/test.rp2040.yaml new file mode 100644 index 0000000000..36aec73113 --- /dev/null +++ b/tests/components/ufire_ise/test.rp2040.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ise.calibrate_probe_high: + id: ufire_ise_sensor + solution: 7.0 + - ufire_ise.calibrate_probe_low: + id: ufire_ise_sensor + solution: 4.0 + - ufire_ise.reset: + +i2c: + - id: i2c_ufire_ise + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ise + id: ufire_ise_sensor + temperature_sensor: test_sensor + ph: + name: Ufire pH diff --git a/tests/components/uln2003/test.esp32-c3-idf.yaml b/tests/components/uln2003/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..2d19d4dba3 --- /dev/null +++ b/tests/components/uln2003/test.esp32-c3-idf.yaml @@ -0,0 +1,29 @@ +esphome: + on_boot: + then: + - stepper.report_position: + id: uln2003_stepper + position: 250 + - stepper.set_target: + id: uln2003_stepper + target: 250 + - stepper.set_acceleration: + id: uln2003_stepper + acceleration: 250 steps/s^2 + - stepper.set_deceleration: + id: uln2003_stepper + deceleration: 250 steps/s^2 + - stepper.set_speed: + id: uln2003_stepper + speed: 250 steps/s + +stepper: + - platform: uln2003 + id: uln2003_stepper + pin_a: 0 + pin_b: 1 + pin_c: 2 + pin_d: 3 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/uln2003/test.esp32-c3.yaml b/tests/components/uln2003/test.esp32-c3.yaml new file mode 100644 index 0000000000..2d19d4dba3 --- /dev/null +++ b/tests/components/uln2003/test.esp32-c3.yaml @@ -0,0 +1,29 @@ +esphome: + on_boot: + then: + - stepper.report_position: + id: uln2003_stepper + position: 250 + - stepper.set_target: + id: uln2003_stepper + target: 250 + - stepper.set_acceleration: + id: uln2003_stepper + acceleration: 250 steps/s^2 + - stepper.set_deceleration: + id: uln2003_stepper + deceleration: 250 steps/s^2 + - stepper.set_speed: + id: uln2003_stepper + speed: 250 steps/s + +stepper: + - platform: uln2003 + id: uln2003_stepper + pin_a: 0 + pin_b: 1 + pin_c: 2 + pin_d: 3 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/uln2003/test.esp32-idf.yaml b/tests/components/uln2003/test.esp32-idf.yaml new file mode 100644 index 0000000000..61a6e94396 --- /dev/null +++ b/tests/components/uln2003/test.esp32-idf.yaml @@ -0,0 +1,29 @@ +esphome: + on_boot: + then: + - stepper.report_position: + id: uln2003_stepper + position: 250 + - stepper.set_target: + id: uln2003_stepper + target: 250 + - stepper.set_acceleration: + id: uln2003_stepper + acceleration: 250 steps/s^2 + - stepper.set_deceleration: + id: uln2003_stepper + deceleration: 250 steps/s^2 + - stepper.set_speed: + id: uln2003_stepper + speed: 250 steps/s + +stepper: + - platform: uln2003 + id: uln2003_stepper + pin_a: 12 + pin_b: 13 + pin_c: 14 + pin_d: 15 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/uln2003/test.esp32.yaml b/tests/components/uln2003/test.esp32.yaml new file mode 100644 index 0000000000..61a6e94396 --- /dev/null +++ b/tests/components/uln2003/test.esp32.yaml @@ -0,0 +1,29 @@ +esphome: + on_boot: + then: + - stepper.report_position: + id: uln2003_stepper + position: 250 + - stepper.set_target: + id: uln2003_stepper + target: 250 + - stepper.set_acceleration: + id: uln2003_stepper + acceleration: 250 steps/s^2 + - stepper.set_deceleration: + id: uln2003_stepper + deceleration: 250 steps/s^2 + - stepper.set_speed: + id: uln2003_stepper + speed: 250 steps/s + +stepper: + - platform: uln2003 + id: uln2003_stepper + pin_a: 12 + pin_b: 13 + pin_c: 14 + pin_d: 15 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/uln2003/test.esp8266.yaml b/tests/components/uln2003/test.esp8266.yaml new file mode 100644 index 0000000000..61a6e94396 --- /dev/null +++ b/tests/components/uln2003/test.esp8266.yaml @@ -0,0 +1,29 @@ +esphome: + on_boot: + then: + - stepper.report_position: + id: uln2003_stepper + position: 250 + - stepper.set_target: + id: uln2003_stepper + target: 250 + - stepper.set_acceleration: + id: uln2003_stepper + acceleration: 250 steps/s^2 + - stepper.set_deceleration: + id: uln2003_stepper + deceleration: 250 steps/s^2 + - stepper.set_speed: + id: uln2003_stepper + speed: 250 steps/s + +stepper: + - platform: uln2003 + id: uln2003_stepper + pin_a: 12 + pin_b: 13 + pin_c: 14 + pin_d: 15 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/uln2003/test.rp2040.yaml b/tests/components/uln2003/test.rp2040.yaml new file mode 100644 index 0000000000..2d19d4dba3 --- /dev/null +++ b/tests/components/uln2003/test.rp2040.yaml @@ -0,0 +1,29 @@ +esphome: + on_boot: + then: + - stepper.report_position: + id: uln2003_stepper + position: 250 + - stepper.set_target: + id: uln2003_stepper + target: 250 + - stepper.set_acceleration: + id: uln2003_stepper + acceleration: 250 steps/s^2 + - stepper.set_deceleration: + id: uln2003_stepper + deceleration: 250 steps/s^2 + - stepper.set_speed: + id: uln2003_stepper + speed: 250 steps/s + +stepper: + - platform: uln2003 + id: uln2003_stepper + pin_a: 0 + pin_b: 1 + pin_c: 2 + pin_d: 3 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/ultrasonic/test.esp32-c3-idf.yaml b/tests/components/ultrasonic/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..f1f673d918 --- /dev/null +++ b/tests/components/ultrasonic/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: ultrasonic + id: ultrasonic_sensor1 + name: Ultrasonic Sensor + echo_pin: 4 + trigger_pin: 5 + timeout: 5.5m diff --git a/tests/components/ultrasonic/test.esp32-c3.yaml b/tests/components/ultrasonic/test.esp32-c3.yaml new file mode 100644 index 0000000000..f1f673d918 --- /dev/null +++ b/tests/components/ultrasonic/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: ultrasonic + id: ultrasonic_sensor1 + name: Ultrasonic Sensor + echo_pin: 4 + trigger_pin: 5 + timeout: 5.5m diff --git a/tests/components/ultrasonic/test.esp32-idf.yaml b/tests/components/ultrasonic/test.esp32-idf.yaml new file mode 100644 index 0000000000..f1f673d918 --- /dev/null +++ b/tests/components/ultrasonic/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: ultrasonic + id: ultrasonic_sensor1 + name: Ultrasonic Sensor + echo_pin: 4 + trigger_pin: 5 + timeout: 5.5m diff --git a/tests/components/ultrasonic/test.esp32.yaml b/tests/components/ultrasonic/test.esp32.yaml new file mode 100644 index 0000000000..f1f673d918 --- /dev/null +++ b/tests/components/ultrasonic/test.esp32.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: ultrasonic + id: ultrasonic_sensor1 + name: Ultrasonic Sensor + echo_pin: 4 + trigger_pin: 5 + timeout: 5.5m diff --git a/tests/components/ultrasonic/test.esp8266.yaml b/tests/components/ultrasonic/test.esp8266.yaml new file mode 100644 index 0000000000..f1f673d918 --- /dev/null +++ b/tests/components/ultrasonic/test.esp8266.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: ultrasonic + id: ultrasonic_sensor1 + name: Ultrasonic Sensor + echo_pin: 4 + trigger_pin: 5 + timeout: 5.5m diff --git a/tests/components/ultrasonic/test.rp2040.yaml b/tests/components/ultrasonic/test.rp2040.yaml new file mode 100644 index 0000000000..f1f673d918 --- /dev/null +++ b/tests/components/ultrasonic/test.rp2040.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: ultrasonic + id: ultrasonic_sensor1 + name: Ultrasonic Sensor + echo_pin: 4 + trigger_pin: 5 + timeout: 5.5m diff --git a/tests/components/uponor_smatrix/common.yaml b/tests/components/uponor_smatrix/common.yaml new file mode 100644 index 0000000000..cfdbacaa4c --- /dev/null +++ b/tests/components/uponor_smatrix/common.yaml @@ -0,0 +1,38 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uponor_uart + baud_rate: 19200 + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + +time: + - platform: sntp + id: sntp_time + servers: + - 0.pool.ntp.org + - 1.pool.ntp.org + - 192.168.178.1 + +uponor_smatrix: + uart_id: uponor_uart + address: 0x110B + time_id: sntp_time + time_device_address: 0xDE13 + +climate: + - platform: uponor_smatrix + address: 0xDE13 + name: Thermostat Living Room + +sensor: + - platform: uponor_smatrix + address: 0xDE13 + humidity: + name: Thermostat Humidity Living Room + temperature: + name: Thermostat Temperature Living Room + external_temperature: + name: Thermostat Floor Temperature Living Room diff --git a/tests/components/uponor_smatrix/test.esp32-c3-idf.yaml b/tests/components/uponor_smatrix/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..b516342f3b --- /dev/null +++ b/tests/components/uponor_smatrix/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/uponor_smatrix/test.esp32-c3.yaml b/tests/components/uponor_smatrix/test.esp32-c3.yaml new file mode 100644 index 0000000000..b516342f3b --- /dev/null +++ b/tests/components/uponor_smatrix/test.esp32-c3.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/uponor_smatrix/test.esp32-idf.yaml b/tests/components/uponor_smatrix/test.esp32-idf.yaml new file mode 100644 index 0000000000..f486544afa --- /dev/null +++ b/tests/components/uponor_smatrix/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 + +<<: !include common.yaml diff --git a/tests/components/uponor_smatrix/test.esp32.yaml b/tests/components/uponor_smatrix/test.esp32.yaml new file mode 100644 index 0000000000..f486544afa --- /dev/null +++ b/tests/components/uponor_smatrix/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 + +<<: !include common.yaml diff --git a/tests/components/uponor_smatrix/test.esp8266.yaml b/tests/components/uponor_smatrix/test.esp8266.yaml new file mode 100644 index 0000000000..b516342f3b --- /dev/null +++ b/tests/components/uponor_smatrix/test.esp8266.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/uponor_smatrix/test.rp2040.yaml b/tests/components/uponor_smatrix/test.rp2040.yaml new file mode 100644 index 0000000000..b516342f3b --- /dev/null +++ b/tests/components/uponor_smatrix/test.rp2040.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/uptime/test.esp32-c3-idf.yaml b/tests/components/uptime/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..872a0e7402 --- /dev/null +++ b/tests/components/uptime/test.esp32-c3-idf.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: uptime + name: Uptime Sensor diff --git a/tests/components/uptime/test.esp32-c3.yaml b/tests/components/uptime/test.esp32-c3.yaml new file mode 100644 index 0000000000..872a0e7402 --- /dev/null +++ b/tests/components/uptime/test.esp32-c3.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: uptime + name: Uptime Sensor diff --git a/tests/components/uptime/test.esp32-idf.yaml b/tests/components/uptime/test.esp32-idf.yaml new file mode 100644 index 0000000000..872a0e7402 --- /dev/null +++ b/tests/components/uptime/test.esp32-idf.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: uptime + name: Uptime Sensor diff --git a/tests/components/uptime/test.esp32.yaml b/tests/components/uptime/test.esp32.yaml new file mode 100644 index 0000000000..872a0e7402 --- /dev/null +++ b/tests/components/uptime/test.esp32.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: uptime + name: Uptime Sensor diff --git a/tests/components/uptime/test.esp8266.yaml b/tests/components/uptime/test.esp8266.yaml new file mode 100644 index 0000000000..872a0e7402 --- /dev/null +++ b/tests/components/uptime/test.esp8266.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: uptime + name: Uptime Sensor diff --git a/tests/components/uptime/test.rp2040.yaml b/tests/components/uptime/test.rp2040.yaml new file mode 100644 index 0000000000..872a0e7402 --- /dev/null +++ b/tests/components/uptime/test.rp2040.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: uptime + name: Uptime Sensor diff --git a/tests/components/vbus/test.esp32-c3-idf.yaml b/tests/components/vbus/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..67ee542031 --- /dev/null +++ b/tests/components/vbus/test.esp32-c3-idf.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_vbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +vbus: + +binary_sensor: + - platform: vbus + model: deltasol_bs_plus + relay1: + name: Relay 1 On + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +sensor: + - platform: vbus + model: deltasol c + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time diff --git a/tests/components/vbus/test.esp32-c3.yaml b/tests/components/vbus/test.esp32-c3.yaml new file mode 100644 index 0000000000..67ee542031 --- /dev/null +++ b/tests/components/vbus/test.esp32-c3.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_vbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +vbus: + +binary_sensor: + - platform: vbus + model: deltasol_bs_plus + relay1: + name: Relay 1 On + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +sensor: + - platform: vbus + model: deltasol c + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time diff --git a/tests/components/vbus/test.esp32-idf.yaml b/tests/components/vbus/test.esp32-idf.yaml new file mode 100644 index 0000000000..a0e5ca42cc --- /dev/null +++ b/tests/components/vbus/test.esp32-idf.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_vbus + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +vbus: + +binary_sensor: + - platform: vbus + model: deltasol_bs_plus + relay1: + name: Relay 1 On + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +sensor: + - platform: vbus + model: deltasol c + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time diff --git a/tests/components/vbus/test.esp32.yaml b/tests/components/vbus/test.esp32.yaml new file mode 100644 index 0000000000..a0e5ca42cc --- /dev/null +++ b/tests/components/vbus/test.esp32.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_vbus + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +vbus: + +binary_sensor: + - platform: vbus + model: deltasol_bs_plus + relay1: + name: Relay 1 On + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +sensor: + - platform: vbus + model: deltasol c + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time diff --git a/tests/components/vbus/test.esp8266.yaml b/tests/components/vbus/test.esp8266.yaml new file mode 100644 index 0000000000..67ee542031 --- /dev/null +++ b/tests/components/vbus/test.esp8266.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_vbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +vbus: + +binary_sensor: + - platform: vbus + model: deltasol_bs_plus + relay1: + name: Relay 1 On + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +sensor: + - platform: vbus + model: deltasol c + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time diff --git a/tests/components/vbus/test.rp2040.yaml b/tests/components/vbus/test.rp2040.yaml new file mode 100644 index 0000000000..67ee542031 --- /dev/null +++ b/tests/components/vbus/test.rp2040.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_vbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +vbus: + +binary_sensor: + - platform: vbus + model: deltasol_bs_plus + relay1: + name: Relay 1 On + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +sensor: + - platform: vbus + model: deltasol c + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time diff --git a/tests/components/veml3235/test.esp32-c3-idf.yaml b/tests/components/veml3235/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..1f79c5f50c --- /dev/null +++ b/tests/components/veml3235/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_veml3235 + scl: 5 + sda: 4 + +sensor: + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms diff --git a/tests/components/veml3235/test.esp32-c3.yaml b/tests/components/veml3235/test.esp32-c3.yaml new file mode 100644 index 0000000000..1f79c5f50c --- /dev/null +++ b/tests/components/veml3235/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_veml3235 + scl: 5 + sda: 4 + +sensor: + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms diff --git a/tests/components/veml3235/test.esp32-idf.yaml b/tests/components/veml3235/test.esp32-idf.yaml new file mode 100644 index 0000000000..3442fa4317 --- /dev/null +++ b/tests/components/veml3235/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_veml3235 + scl: 16 + sda: 17 + +sensor: + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms diff --git a/tests/components/veml3235/test.esp32.yaml b/tests/components/veml3235/test.esp32.yaml new file mode 100644 index 0000000000..3442fa4317 --- /dev/null +++ b/tests/components/veml3235/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_veml3235 + scl: 16 + sda: 17 + +sensor: + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms diff --git a/tests/components/veml3235/test.esp8266.yaml b/tests/components/veml3235/test.esp8266.yaml new file mode 100644 index 0000000000..1f79c5f50c --- /dev/null +++ b/tests/components/veml3235/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_veml3235 + scl: 5 + sda: 4 + +sensor: + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms diff --git a/tests/components/veml3235/test.rp2040.yaml b/tests/components/veml3235/test.rp2040.yaml new file mode 100644 index 0000000000..1f79c5f50c --- /dev/null +++ b/tests/components/veml3235/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_veml3235 + scl: 5 + sda: 4 + +sensor: + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms diff --git a/tests/components/veml7700/common.yaml b/tests/components/veml7700/common.yaml new file mode 100644 index 0000000000..6620c8d7e1 --- /dev/null +++ b/tests/components/veml7700/common.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: veml7700 + address: 0x10 + i2c_id: i2c_veml7700 + ambient_light: Ambient light + ambient_light_counts: Ambient light counts + full_spectrum: Full spectrum + full_spectrum_counts: Full spectrum counts + actual_integration_time: Actual integration time + actual_gain: Actual gain diff --git a/tests/components/veml7700/test.esp32-c3-idf.yaml b/tests/components/veml7700/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ce0fa0125b --- /dev/null +++ b/tests/components/veml7700/test.esp32-c3-idf.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_veml7700 + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/veml7700/test.esp32-c3.yaml b/tests/components/veml7700/test.esp32-c3.yaml new file mode 100644 index 0000000000..ce0fa0125b --- /dev/null +++ b/tests/components/veml7700/test.esp32-c3.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_veml7700 + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/veml7700/test.esp32-idf.yaml b/tests/components/veml7700/test.esp32-idf.yaml new file mode 100644 index 0000000000..4b812a1392 --- /dev/null +++ b/tests/components/veml7700/test.esp32-idf.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_veml7700 + scl: 16 + sda: 17 + +<<: !include common.yaml diff --git a/tests/components/veml7700/test.esp32.yaml b/tests/components/veml7700/test.esp32.yaml new file mode 100644 index 0000000000..4b812a1392 --- /dev/null +++ b/tests/components/veml7700/test.esp32.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_veml7700 + scl: 16 + sda: 17 + +<<: !include common.yaml diff --git a/tests/components/veml7700/test.esp8266.yaml b/tests/components/veml7700/test.esp8266.yaml new file mode 100644 index 0000000000..ce0fa0125b --- /dev/null +++ b/tests/components/veml7700/test.esp8266.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_veml7700 + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/veml7700/test.rp2040.yaml b/tests/components/veml7700/test.rp2040.yaml new file mode 100644 index 0000000000..ce0fa0125b --- /dev/null +++ b/tests/components/veml7700/test.rp2040.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_veml7700 + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/version/test.esp32-c3-idf.yaml b/tests/components/version/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..7713afc37c --- /dev/null +++ b/tests/components/version/test.esp32-c3-idf.yaml @@ -0,0 +1,3 @@ +text_sensor: + - platform: version + name: "ESPHome Version" diff --git a/tests/components/version/test.esp32-c3.yaml b/tests/components/version/test.esp32-c3.yaml new file mode 100644 index 0000000000..7713afc37c --- /dev/null +++ b/tests/components/version/test.esp32-c3.yaml @@ -0,0 +1,3 @@ +text_sensor: + - platform: version + name: "ESPHome Version" diff --git a/tests/components/version/test.esp32-idf.yaml b/tests/components/version/test.esp32-idf.yaml new file mode 100644 index 0000000000..7713afc37c --- /dev/null +++ b/tests/components/version/test.esp32-idf.yaml @@ -0,0 +1,3 @@ +text_sensor: + - platform: version + name: "ESPHome Version" diff --git a/tests/components/version/test.esp32.yaml b/tests/components/version/test.esp32.yaml new file mode 100644 index 0000000000..7713afc37c --- /dev/null +++ b/tests/components/version/test.esp32.yaml @@ -0,0 +1,3 @@ +text_sensor: + - platform: version + name: "ESPHome Version" diff --git a/tests/components/version/test.esp8266.yaml b/tests/components/version/test.esp8266.yaml new file mode 100644 index 0000000000..7713afc37c --- /dev/null +++ b/tests/components/version/test.esp8266.yaml @@ -0,0 +1,3 @@ +text_sensor: + - platform: version + name: "ESPHome Version" diff --git a/tests/components/version/test.rp2040.yaml b/tests/components/version/test.rp2040.yaml new file mode 100644 index 0000000000..7713afc37c --- /dev/null +++ b/tests/components/version/test.rp2040.yaml @@ -0,0 +1,3 @@ +text_sensor: + - platform: version + name: "ESPHome Version" diff --git a/tests/components/vl53l0x/test.esp32-c3-idf.yaml b/tests/components/vl53l0x/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..832f7dcfbc --- /dev/null +++ b/tests/components/vl53l0x/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_vl53l0x + scl: 5 + sda: 4 + +sensor: + - platform: vl53l0x + name: VL53L0x Distance + address: 0x29 + enable_pin: 3 + timeout: 200us + update_interval: 60s diff --git a/tests/components/vl53l0x/test.esp32-c3.yaml b/tests/components/vl53l0x/test.esp32-c3.yaml new file mode 100644 index 0000000000..832f7dcfbc --- /dev/null +++ b/tests/components/vl53l0x/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_vl53l0x + scl: 5 + sda: 4 + +sensor: + - platform: vl53l0x + name: VL53L0x Distance + address: 0x29 + enable_pin: 3 + timeout: 200us + update_interval: 60s diff --git a/tests/components/vl53l0x/test.esp32-idf.yaml b/tests/components/vl53l0x/test.esp32-idf.yaml new file mode 100644 index 0000000000..8f35de0e72 --- /dev/null +++ b/tests/components/vl53l0x/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_vl53l0x + scl: 16 + sda: 17 + +sensor: + - platform: vl53l0x + name: VL53L0x Distance + address: 0x29 + enable_pin: 3 + timeout: 200us + update_interval: 60s diff --git a/tests/components/vl53l0x/test.esp32.yaml b/tests/components/vl53l0x/test.esp32.yaml new file mode 100644 index 0000000000..8f35de0e72 --- /dev/null +++ b/tests/components/vl53l0x/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_vl53l0x + scl: 16 + sda: 17 + +sensor: + - platform: vl53l0x + name: VL53L0x Distance + address: 0x29 + enable_pin: 3 + timeout: 200us + update_interval: 60s diff --git a/tests/components/vl53l0x/test.esp8266.yaml b/tests/components/vl53l0x/test.esp8266.yaml new file mode 100644 index 0000000000..832f7dcfbc --- /dev/null +++ b/tests/components/vl53l0x/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_vl53l0x + scl: 5 + sda: 4 + +sensor: + - platform: vl53l0x + name: VL53L0x Distance + address: 0x29 + enable_pin: 3 + timeout: 200us + update_interval: 60s diff --git a/tests/components/vl53l0x/test.rp2040.yaml b/tests/components/vl53l0x/test.rp2040.yaml new file mode 100644 index 0000000000..832f7dcfbc --- /dev/null +++ b/tests/components/vl53l0x/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_vl53l0x + scl: 5 + sda: 4 + +sensor: + - platform: vl53l0x + name: VL53L0x Distance + address: 0x29 + enable_pin: 3 + timeout: 200us + update_interval: 60s diff --git a/tests/components/voice_assistant/test.esp32-c3-idf.yaml b/tests/components/voice_assistant/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..248ae4d0dc --- /dev/null +++ b/tests/components/voice_assistant/test.esp32-c3-idf.yaml @@ -0,0 +1,57 @@ +esphome: + on_boot: + then: + - voice_assistant.start + - voice_assistant.start_continuous + - voice_assistant.stop + +wifi: + ssid: MySSID + password: password1 + +api: + +i2s_audio: + i2s_lrclk_pin: 6 + i2s_bclk_pin: 7 + i2s_mclk_pin: 5 + +microphone: + - platform: i2s_audio + id: mic_id_external + i2s_din_pin: 3 + adc_type: external + pdm: false + +speaker: + - platform: i2s_audio + id: speaker_id + dac_type: external + i2s_dout_pin: 2 + mode: mono + +voice_assistant: + microphone: mic_id_external + speaker: speaker_id + on_listening: + - logger.log: "Voice assistant microphone listening" + on_start: + - logger.log: "Voice assistant started" + on_stt_end: + - logger.log: + format: "Voice assistant STT ended with result %s" + args: [x.c_str()] + on_tts_start: + - logger.log: + format: "Voice assistant TTS started with text %s" + args: [x.c_str()] + on_tts_end: + - logger.log: + format: "Voice assistant TTS ended with url %s" + args: [x.c_str()] + on_end: + - logger.log: "Voice assistant ended" + on_error: + - logger.log: + format: "Voice assistant error - code %s, message: %s" + args: [code.c_str(), message.c_str()] diff --git a/tests/components/voice_assistant/test.esp32-c3.yaml b/tests/components/voice_assistant/test.esp32-c3.yaml new file mode 100644 index 0000000000..248ae4d0dc --- /dev/null +++ b/tests/components/voice_assistant/test.esp32-c3.yaml @@ -0,0 +1,57 @@ +esphome: + on_boot: + then: + - voice_assistant.start + - voice_assistant.start_continuous + - voice_assistant.stop + +wifi: + ssid: MySSID + password: password1 + +api: + +i2s_audio: + i2s_lrclk_pin: 6 + i2s_bclk_pin: 7 + i2s_mclk_pin: 5 + +microphone: + - platform: i2s_audio + id: mic_id_external + i2s_din_pin: 3 + adc_type: external + pdm: false + +speaker: + - platform: i2s_audio + id: speaker_id + dac_type: external + i2s_dout_pin: 2 + mode: mono + +voice_assistant: + microphone: mic_id_external + speaker: speaker_id + on_listening: + - logger.log: "Voice assistant microphone listening" + on_start: + - logger.log: "Voice assistant started" + on_stt_end: + - logger.log: + format: "Voice assistant STT ended with result %s" + args: [x.c_str()] + on_tts_start: + - logger.log: + format: "Voice assistant TTS started with text %s" + args: [x.c_str()] + on_tts_end: + - logger.log: + format: "Voice assistant TTS ended with url %s" + args: [x.c_str()] + on_end: + - logger.log: "Voice assistant ended" + on_error: + - logger.log: + format: "Voice assistant error - code %s, message: %s" + args: [code.c_str(), message.c_str()] diff --git a/tests/components/voice_assistant/test.esp32-idf.yaml b/tests/components/voice_assistant/test.esp32-idf.yaml new file mode 100644 index 0000000000..2e0209311d --- /dev/null +++ b/tests/components/voice_assistant/test.esp32-idf.yaml @@ -0,0 +1,57 @@ +esphome: + on_boot: + then: + - voice_assistant.start + - voice_assistant.start_continuous + - voice_assistant.stop + +wifi: + ssid: MySSID + password: password1 + +api: + +i2s_audio: + i2s_lrclk_pin: 16 + i2s_bclk_pin: 17 + i2s_mclk_pin: 15 + +microphone: + - platform: i2s_audio + id: mic_id_external + i2s_din_pin: 13 + adc_type: external + pdm: false + +speaker: + - platform: i2s_audio + id: speaker_id + dac_type: external + i2s_dout_pin: 12 + mode: mono + +voice_assistant: + microphone: mic_id_external + speaker: speaker_id + on_listening: + - logger.log: "Voice assistant microphone listening" + on_start: + - logger.log: "Voice assistant started" + on_stt_end: + - logger.log: + format: "Voice assistant STT ended with result %s" + args: [x.c_str()] + on_tts_start: + - logger.log: + format: "Voice assistant TTS started with text %s" + args: [x.c_str()] + on_tts_end: + - logger.log: + format: "Voice assistant TTS ended with url %s" + args: [x.c_str()] + on_end: + - logger.log: "Voice assistant ended" + on_error: + - logger.log: + format: "Voice assistant error - code %s, message: %s" + args: [code.c_str(), message.c_str()] diff --git a/tests/components/voice_assistant/test.esp32.yaml b/tests/components/voice_assistant/test.esp32.yaml new file mode 100644 index 0000000000..2e0209311d --- /dev/null +++ b/tests/components/voice_assistant/test.esp32.yaml @@ -0,0 +1,57 @@ +esphome: + on_boot: + then: + - voice_assistant.start + - voice_assistant.start_continuous + - voice_assistant.stop + +wifi: + ssid: MySSID + password: password1 + +api: + +i2s_audio: + i2s_lrclk_pin: 16 + i2s_bclk_pin: 17 + i2s_mclk_pin: 15 + +microphone: + - platform: i2s_audio + id: mic_id_external + i2s_din_pin: 13 + adc_type: external + pdm: false + +speaker: + - platform: i2s_audio + id: speaker_id + dac_type: external + i2s_dout_pin: 12 + mode: mono + +voice_assistant: + microphone: mic_id_external + speaker: speaker_id + on_listening: + - logger.log: "Voice assistant microphone listening" + on_start: + - logger.log: "Voice assistant started" + on_stt_end: + - logger.log: + format: "Voice assistant STT ended with result %s" + args: [x.c_str()] + on_tts_start: + - logger.log: + format: "Voice assistant TTS started with text %s" + args: [x.c_str()] + on_tts_end: + - logger.log: + format: "Voice assistant TTS ended with url %s" + args: [x.c_str()] + on_end: + - logger.log: "Voice assistant ended" + on_error: + - logger.log: + format: "Voice assistant error - code %s, message: %s" + args: [code.c_str(), message.c_str()] diff --git a/tests/components/wake_on_lan/test.esp32-c3.yaml b/tests/components/wake_on_lan/test.esp32-c3.yaml new file mode 100644 index 0000000000..6a5351b624 --- /dev/null +++ b/tests/components/wake_on_lan/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +wifi: + ssid: MySSID + password: password1 + +button: + - platform: wake_on_lan + id: wol_1 + name: wol_test_1 + target_mac_address: 12:34:56:78:90:ab diff --git a/tests/components/wake_on_lan/test.esp32.yaml b/tests/components/wake_on_lan/test.esp32.yaml new file mode 100644 index 0000000000..6a5351b624 --- /dev/null +++ b/tests/components/wake_on_lan/test.esp32.yaml @@ -0,0 +1,9 @@ +wifi: + ssid: MySSID + password: password1 + +button: + - platform: wake_on_lan + id: wol_1 + name: wol_test_1 + target_mac_address: 12:34:56:78:90:ab diff --git a/tests/components/wake_on_lan/test.esp8266.yaml b/tests/components/wake_on_lan/test.esp8266.yaml new file mode 100644 index 0000000000..6a5351b624 --- /dev/null +++ b/tests/components/wake_on_lan/test.esp8266.yaml @@ -0,0 +1,9 @@ +wifi: + ssid: MySSID + password: password1 + +button: + - platform: wake_on_lan + id: wol_1 + name: wol_test_1 + target_mac_address: 12:34:56:78:90:ab diff --git a/tests/components/wake_on_lan/test.rp2040.yaml b/tests/components/wake_on_lan/test.rp2040.yaml new file mode 100644 index 0000000000..6a5351b624 --- /dev/null +++ b/tests/components/wake_on_lan/test.rp2040.yaml @@ -0,0 +1,9 @@ +wifi: + ssid: MySSID + password: password1 + +button: + - platform: wake_on_lan + id: wol_1 + name: wol_test_1 + target_mac_address: 12:34:56:78:90:ab diff --git a/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml b/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..1c4547b7b4 --- /dev/null +++ b/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml @@ -0,0 +1,107 @@ +spi: + - id: spi_waveshare_epaper + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.13in-ttgo-b1 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90in + full_update_every: 30 + reset_duration: 200ms + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90inv2 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-b + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-bv2 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 1.54in-m5coreink-m09 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp32-c3.yaml b/tests/components/waveshare_epaper/test.esp32-c3.yaml new file mode 100644 index 0000000000..1c4547b7b4 --- /dev/null +++ b/tests/components/waveshare_epaper/test.esp32-c3.yaml @@ -0,0 +1,107 @@ +spi: + - id: spi_waveshare_epaper + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.13in-ttgo-b1 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90in + full_update_every: 30 + reset_duration: 200ms + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90inv2 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-b + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-bv2 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 1.54in-m5coreink-m09 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp32-idf.yaml b/tests/components/waveshare_epaper/test.esp32-idf.yaml new file mode 100644 index 0000000000..b6082fcfbf --- /dev/null +++ b/tests/components/waveshare_epaper/test.esp32-idf.yaml @@ -0,0 +1,107 @@ +spi: + - id: spi_bme280 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.13in-ttgo-b1 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90in + full_update_every: 30 + reset_duration: 200ms + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90inv2 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-b + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-bv2 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 1.54in-m5coreink-m09 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp32.yaml b/tests/components/waveshare_epaper/test.esp32.yaml new file mode 100644 index 0000000000..cc6c665e7d --- /dev/null +++ b/tests/components/waveshare_epaper/test.esp32.yaml @@ -0,0 +1,172 @@ +--- +spi: + - id: spi_id_1 + clk_pin: + number: GPIO18 + mosi_pin: + number: GPIO23 + miso_pin: + number: GPIO19 + interface: hardware + +display: + - platform: waveshare_epaper + model: 2.13in-ttgo-b1 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.13in-ttgo-b74 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.90in + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + full_update_every: 30 + reset_duration: 200ms + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.90inv2 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.70in-b + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.70in-bv2 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 1.54in-m5coreink-m09 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.13inv3 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.13inv2 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp8266.yaml b/tests/components/waveshare_epaper/test.esp8266.yaml new file mode 100644 index 0000000000..1f076a67be --- /dev/null +++ b/tests/components/waveshare_epaper/test.esp8266.yaml @@ -0,0 +1,107 @@ +spi: + - id: spi_bme280 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.13in-ttgo-b1 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90in + full_update_every: 30 + reset_duration: 200ms + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90inv2 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-b + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-bv2 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 1.54in-m5coreink-m09 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.rp2040.yaml b/tests/components/waveshare_epaper/test.rp2040.yaml new file mode 100644 index 0000000000..6050062d7e --- /dev/null +++ b/tests/components/waveshare_epaper/test.rp2040.yaml @@ -0,0 +1,107 @@ +spi: + - id: spi_bme280 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 2.13in-ttgo-b1 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 2.90in + full_update_every: 30 + reset_duration: 200ms + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 2.90inv2 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 2.70in-b + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 2.70in-bv2 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 1.54in-m5coreink-m09 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/web_server/test.esp32-c3-idf.yaml b/tests/components/web_server/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..94388726c3 --- /dev/null +++ b/tests/components/web_server/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +wifi: + ssid: MySSID + password: password1 + +web_server: + port: 8080 + version: 2 diff --git a/tests/components/web_server/test.esp32-c3.yaml b/tests/components/web_server/test.esp32-c3.yaml new file mode 100644 index 0000000000..94388726c3 --- /dev/null +++ b/tests/components/web_server/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +wifi: + ssid: MySSID + password: password1 + +web_server: + port: 8080 + version: 2 diff --git a/tests/components/web_server/test.esp32-idf.yaml b/tests/components/web_server/test.esp32-idf.yaml new file mode 100644 index 0000000000..94388726c3 --- /dev/null +++ b/tests/components/web_server/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +wifi: + ssid: MySSID + password: password1 + +web_server: + port: 8080 + version: 2 diff --git a/tests/components/web_server/test.esp32.yaml b/tests/components/web_server/test.esp32.yaml new file mode 100644 index 0000000000..94388726c3 --- /dev/null +++ b/tests/components/web_server/test.esp32.yaml @@ -0,0 +1,7 @@ +wifi: + ssid: MySSID + password: password1 + +web_server: + port: 8080 + version: 2 diff --git a/tests/components/web_server/test.esp8266.yaml b/tests/components/web_server/test.esp8266.yaml new file mode 100644 index 0000000000..94388726c3 --- /dev/null +++ b/tests/components/web_server/test.esp8266.yaml @@ -0,0 +1,7 @@ +wifi: + ssid: MySSID + password: password1 + +web_server: + port: 8080 + version: 2 diff --git a/tests/components/whirlpool/test.esp32-c3-idf.yaml b/tests/components/whirlpool/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4d0afc39a4 --- /dev/null +++ b/tests/components/whirlpool/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whirlpool + name: Whirlpool Climate diff --git a/tests/components/whirlpool/test.esp32-c3.yaml b/tests/components/whirlpool/test.esp32-c3.yaml new file mode 100644 index 0000000000..4d0afc39a4 --- /dev/null +++ b/tests/components/whirlpool/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whirlpool + name: Whirlpool Climate diff --git a/tests/components/whirlpool/test.esp32-idf.yaml b/tests/components/whirlpool/test.esp32-idf.yaml new file mode 100644 index 0000000000..4d0afc39a4 --- /dev/null +++ b/tests/components/whirlpool/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whirlpool + name: Whirlpool Climate diff --git a/tests/components/whirlpool/test.esp32.yaml b/tests/components/whirlpool/test.esp32.yaml new file mode 100644 index 0000000000..4d0afc39a4 --- /dev/null +++ b/tests/components/whirlpool/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whirlpool + name: Whirlpool Climate diff --git a/tests/components/whirlpool/test.esp8266.yaml b/tests/components/whirlpool/test.esp8266.yaml new file mode 100644 index 0000000000..efd530c160 --- /dev/null +++ b/tests/components/whirlpool/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: whirlpool + name: Whirlpool Climate diff --git a/tests/components/whynter/test.esp32-c3-idf.yaml b/tests/components/whynter/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..dc8fb9584d --- /dev/null +++ b/tests/components/whynter/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whynter + name: Whynter Climate diff --git a/tests/components/whynter/test.esp32-c3.yaml b/tests/components/whynter/test.esp32-c3.yaml new file mode 100644 index 0000000000..dc8fb9584d --- /dev/null +++ b/tests/components/whynter/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whynter + name: Whynter Climate diff --git a/tests/components/whynter/test.esp32-idf.yaml b/tests/components/whynter/test.esp32-idf.yaml new file mode 100644 index 0000000000..dc8fb9584d --- /dev/null +++ b/tests/components/whynter/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whynter + name: Whynter Climate diff --git a/tests/components/whynter/test.esp32.yaml b/tests/components/whynter/test.esp32.yaml new file mode 100644 index 0000000000..dc8fb9584d --- /dev/null +++ b/tests/components/whynter/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whynter + name: Whynter Climate diff --git a/tests/components/whynter/test.esp8266.yaml b/tests/components/whynter/test.esp8266.yaml new file mode 100644 index 0000000000..a656a7427d --- /dev/null +++ b/tests/components/whynter/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: whynter + name: Whynter Climate diff --git a/tests/components/wiegand/test.esp32-c3-idf.yaml b/tests/components/wiegand/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4e15a44b89 --- /dev/null +++ b/tests/components/wiegand/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +wiegand: + - id: test_wiegand + d0: 5 + d1: 4 + on_key: + - lambda: ESP_LOGI("KEY", "Received key %d", x); + on_tag: + - lambda: ESP_LOGI("TAG", "Received tag %s", x.c_str()); + on_raw: + - lambda: ESP_LOGI("RAW", "Received raw %d bits, value %llx", bits, value); diff --git a/tests/components/wiegand/test.esp32-c3.yaml b/tests/components/wiegand/test.esp32-c3.yaml new file mode 100644 index 0000000000..4e15a44b89 --- /dev/null +++ b/tests/components/wiegand/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +wiegand: + - id: test_wiegand + d0: 5 + d1: 4 + on_key: + - lambda: ESP_LOGI("KEY", "Received key %d", x); + on_tag: + - lambda: ESP_LOGI("TAG", "Received tag %s", x.c_str()); + on_raw: + - lambda: ESP_LOGI("RAW", "Received raw %d bits, value %llx", bits, value); diff --git a/tests/components/wiegand/test.esp32-idf.yaml b/tests/components/wiegand/test.esp32-idf.yaml new file mode 100644 index 0000000000..4e15a44b89 --- /dev/null +++ b/tests/components/wiegand/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +wiegand: + - id: test_wiegand + d0: 5 + d1: 4 + on_key: + - lambda: ESP_LOGI("KEY", "Received key %d", x); + on_tag: + - lambda: ESP_LOGI("TAG", "Received tag %s", x.c_str()); + on_raw: + - lambda: ESP_LOGI("RAW", "Received raw %d bits, value %llx", bits, value); diff --git a/tests/components/wiegand/test.esp32.yaml b/tests/components/wiegand/test.esp32.yaml new file mode 100644 index 0000000000..4e15a44b89 --- /dev/null +++ b/tests/components/wiegand/test.esp32.yaml @@ -0,0 +1,10 @@ +wiegand: + - id: test_wiegand + d0: 5 + d1: 4 + on_key: + - lambda: ESP_LOGI("KEY", "Received key %d", x); + on_tag: + - lambda: ESP_LOGI("TAG", "Received tag %s", x.c_str()); + on_raw: + - lambda: ESP_LOGI("RAW", "Received raw %d bits, value %llx", bits, value); diff --git a/tests/components/wiegand/test.esp8266.yaml b/tests/components/wiegand/test.esp8266.yaml new file mode 100644 index 0000000000..4e15a44b89 --- /dev/null +++ b/tests/components/wiegand/test.esp8266.yaml @@ -0,0 +1,10 @@ +wiegand: + - id: test_wiegand + d0: 5 + d1: 4 + on_key: + - lambda: ESP_LOGI("KEY", "Received key %d", x); + on_tag: + - lambda: ESP_LOGI("TAG", "Received tag %s", x.c_str()); + on_raw: + - lambda: ESP_LOGI("RAW", "Received raw %d bits, value %llx", bits, value); diff --git a/tests/components/wiegand/test.rp2040.yaml b/tests/components/wiegand/test.rp2040.yaml new file mode 100644 index 0000000000..4e15a44b89 --- /dev/null +++ b/tests/components/wiegand/test.rp2040.yaml @@ -0,0 +1,10 @@ +wiegand: + - id: test_wiegand + d0: 5 + d1: 4 + on_key: + - lambda: ESP_LOGI("KEY", "Received key %d", x); + on_tag: + - lambda: ESP_LOGI("TAG", "Received tag %s", x.c_str()); + on_raw: + - lambda: ESP_LOGI("RAW", "Received raw %d bits, value %llx", bits, value); diff --git a/tests/components/wifi/test.esp32-c3-idf.yaml b/tests/components/wifi/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..003f6347be --- /dev/null +++ b/tests/components/wifi/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +esphome: + on_boot: + then: + - wifi.disable + - wifi.enable + +wifi: + ssid: MySSID + password: password1 diff --git a/tests/components/wifi/test.esp32-c3.yaml b/tests/components/wifi/test.esp32-c3.yaml new file mode 100644 index 0000000000..003f6347be --- /dev/null +++ b/tests/components/wifi/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +esphome: + on_boot: + then: + - wifi.disable + - wifi.enable + +wifi: + ssid: MySSID + password: password1 diff --git a/tests/components/wifi/test.esp32-idf.yaml b/tests/components/wifi/test.esp32-idf.yaml new file mode 100644 index 0000000000..003f6347be --- /dev/null +++ b/tests/components/wifi/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +esphome: + on_boot: + then: + - wifi.disable + - wifi.enable + +wifi: + ssid: MySSID + password: password1 diff --git a/tests/components/wifi/test.esp32.yaml b/tests/components/wifi/test.esp32.yaml new file mode 100644 index 0000000000..003f6347be --- /dev/null +++ b/tests/components/wifi/test.esp32.yaml @@ -0,0 +1,9 @@ +esphome: + on_boot: + then: + - wifi.disable + - wifi.enable + +wifi: + ssid: MySSID + password: password1 diff --git a/tests/components/wifi/test.esp8266.yaml b/tests/components/wifi/test.esp8266.yaml new file mode 100644 index 0000000000..003f6347be --- /dev/null +++ b/tests/components/wifi/test.esp8266.yaml @@ -0,0 +1,9 @@ +esphome: + on_boot: + then: + - wifi.disable + - wifi.enable + +wifi: + ssid: MySSID + password: password1 diff --git a/tests/components/wifi/test.rp2040.yaml b/tests/components/wifi/test.rp2040.yaml new file mode 100644 index 0000000000..003f6347be --- /dev/null +++ b/tests/components/wifi/test.rp2040.yaml @@ -0,0 +1,9 @@ +esphome: + on_boot: + then: + - wifi.disable + - wifi.enable + +wifi: + ssid: MySSID + password: password1 diff --git a/tests/components/wifi_info/test.esp32-c3-idf.yaml b/tests/components/wifi_info/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..cf5ea563ba --- /dev/null +++ b/tests/components/wifi_info/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +text_sensor: + - platform: wifi_info + scan_results: + name: Scan Results + ip_address: + name: IP Address + ssid: + name: SSID + bssid: + name: BSSID + mac_address: + name: Mac Address + dns_address: + name: DNS ADdress diff --git a/tests/components/wifi_info/test.esp32-c3.yaml b/tests/components/wifi_info/test.esp32-c3.yaml new file mode 100644 index 0000000000..cf5ea563ba --- /dev/null +++ b/tests/components/wifi_info/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +text_sensor: + - platform: wifi_info + scan_results: + name: Scan Results + ip_address: + name: IP Address + ssid: + name: SSID + bssid: + name: BSSID + mac_address: + name: Mac Address + dns_address: + name: DNS ADdress diff --git a/tests/components/wifi_info/test.esp32-idf.yaml b/tests/components/wifi_info/test.esp32-idf.yaml new file mode 100644 index 0000000000..cf5ea563ba --- /dev/null +++ b/tests/components/wifi_info/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +text_sensor: + - platform: wifi_info + scan_results: + name: Scan Results + ip_address: + name: IP Address + ssid: + name: SSID + bssid: + name: BSSID + mac_address: + name: Mac Address + dns_address: + name: DNS ADdress diff --git a/tests/components/wifi_info/test.esp32.yaml b/tests/components/wifi_info/test.esp32.yaml new file mode 100644 index 0000000000..cf5ea563ba --- /dev/null +++ b/tests/components/wifi_info/test.esp32.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +text_sensor: + - platform: wifi_info + scan_results: + name: Scan Results + ip_address: + name: IP Address + ssid: + name: SSID + bssid: + name: BSSID + mac_address: + name: Mac Address + dns_address: + name: DNS ADdress diff --git a/tests/components/wifi_info/test.esp8266.yaml b/tests/components/wifi_info/test.esp8266.yaml new file mode 100644 index 0000000000..cf5ea563ba --- /dev/null +++ b/tests/components/wifi_info/test.esp8266.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +text_sensor: + - platform: wifi_info + scan_results: + name: Scan Results + ip_address: + name: IP Address + ssid: + name: SSID + bssid: + name: BSSID + mac_address: + name: Mac Address + dns_address: + name: DNS ADdress diff --git a/tests/components/wifi_info/test.rp2040.yaml b/tests/components/wifi_info/test.rp2040.yaml new file mode 100644 index 0000000000..cf5ea563ba --- /dev/null +++ b/tests/components/wifi_info/test.rp2040.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +text_sensor: + - platform: wifi_info + scan_results: + name: Scan Results + ip_address: + name: IP Address + ssid: + name: SSID + bssid: + name: BSSID + mac_address: + name: Mac Address + dns_address: + name: DNS ADdress diff --git a/tests/components/wifi_signal/test.esp32-c3-idf.yaml b/tests/components/wifi_signal/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..58d1cab244 --- /dev/null +++ b/tests/components/wifi_signal/test.esp32-c3-idf.yaml @@ -0,0 +1,8 @@ +wifi: + ssid: MySSID + password: password1 + +sensor: + - platform: wifi_signal + name: WiFi Signal Sensor + update_interval: 15s diff --git a/tests/components/wifi_signal/test.esp32-c3.yaml b/tests/components/wifi_signal/test.esp32-c3.yaml new file mode 100644 index 0000000000..58d1cab244 --- /dev/null +++ b/tests/components/wifi_signal/test.esp32-c3.yaml @@ -0,0 +1,8 @@ +wifi: + ssid: MySSID + password: password1 + +sensor: + - platform: wifi_signal + name: WiFi Signal Sensor + update_interval: 15s diff --git a/tests/components/wifi_signal/test.esp32-idf.yaml b/tests/components/wifi_signal/test.esp32-idf.yaml new file mode 100644 index 0000000000..58d1cab244 --- /dev/null +++ b/tests/components/wifi_signal/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +wifi: + ssid: MySSID + password: password1 + +sensor: + - platform: wifi_signal + name: WiFi Signal Sensor + update_interval: 15s diff --git a/tests/components/wifi_signal/test.esp32.yaml b/tests/components/wifi_signal/test.esp32.yaml new file mode 100644 index 0000000000..58d1cab244 --- /dev/null +++ b/tests/components/wifi_signal/test.esp32.yaml @@ -0,0 +1,8 @@ +wifi: + ssid: MySSID + password: password1 + +sensor: + - platform: wifi_signal + name: WiFi Signal Sensor + update_interval: 15s diff --git a/tests/components/wifi_signal/test.esp8266.yaml b/tests/components/wifi_signal/test.esp8266.yaml new file mode 100644 index 0000000000..58d1cab244 --- /dev/null +++ b/tests/components/wifi_signal/test.esp8266.yaml @@ -0,0 +1,8 @@ +wifi: + ssid: MySSID + password: password1 + +sensor: + - platform: wifi_signal + name: WiFi Signal Sensor + update_interval: 15s diff --git a/tests/components/wifi_signal/test.rp2040.yaml b/tests/components/wifi_signal/test.rp2040.yaml new file mode 100644 index 0000000000..58d1cab244 --- /dev/null +++ b/tests/components/wifi_signal/test.rp2040.yaml @@ -0,0 +1,8 @@ +wifi: + ssid: MySSID + password: password1 + +sensor: + - platform: wifi_signal + name: WiFi Signal Sensor + update_interval: 15s diff --git a/tests/components/wireguard/test.esp32-c3-idf.yaml b/tests/components/wireguard/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..37d1727842 --- /dev/null +++ b/tests/components/wireguard/test.esp32-c3-idf.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + +wireguard: + id: vpn + address: 172.16.34.100 + netmask: 255.255.255.0 + # NEVER use the following keys for your vpn, they are now public! + private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8= + peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc= + peer_preshared_key: 20fjM5GRnSolGPC5SRj9ljgIUyQfruv0B0bvLl3Yt60= + peer_endpoint: wg.server.example + peer_persistent_keepalive: 25s + peer_allowed_ips: + - 172.16.34.0/24 + - 192.168.4.0/24 + +binary_sensor: + - platform: wireguard + status: + name: 'WireGuard Status' + enabled: + name: 'WireGuard Enabled' + +sensor: + - platform: wireguard + latest_handshake: + name: 'WireGuard Latest Handshake' + +text_sensor: + - platform: wireguard + address: + name: 'WireGuard Address' + +button: + - platform: template + name: 'Toggle WireGuard' + entity_category: config + on_press: + - if: + condition: wireguard.enabled + then: + - wireguard.disable: + else: + - wireguard.enable: + + - platform: template + name: 'Log WireGuard status' + entity_category: config + on_press: + - if: + condition: wireguard.peer_online + then: + - logger.log: 'wireguard remote peer is online' + else: + - logger.log: 'wireguard remote peer is offline' diff --git a/tests/components/wireguard/test.esp32-c3.yaml b/tests/components/wireguard/test.esp32-c3.yaml new file mode 100644 index 0000000000..37d1727842 --- /dev/null +++ b/tests/components/wireguard/test.esp32-c3.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + +wireguard: + id: vpn + address: 172.16.34.100 + netmask: 255.255.255.0 + # NEVER use the following keys for your vpn, they are now public! + private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8= + peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc= + peer_preshared_key: 20fjM5GRnSolGPC5SRj9ljgIUyQfruv0B0bvLl3Yt60= + peer_endpoint: wg.server.example + peer_persistent_keepalive: 25s + peer_allowed_ips: + - 172.16.34.0/24 + - 192.168.4.0/24 + +binary_sensor: + - platform: wireguard + status: + name: 'WireGuard Status' + enabled: + name: 'WireGuard Enabled' + +sensor: + - platform: wireguard + latest_handshake: + name: 'WireGuard Latest Handshake' + +text_sensor: + - platform: wireguard + address: + name: 'WireGuard Address' + +button: + - platform: template + name: 'Toggle WireGuard' + entity_category: config + on_press: + - if: + condition: wireguard.enabled + then: + - wireguard.disable: + else: + - wireguard.enable: + + - platform: template + name: 'Log WireGuard status' + entity_category: config + on_press: + - if: + condition: wireguard.peer_online + then: + - logger.log: 'wireguard remote peer is online' + else: + - logger.log: 'wireguard remote peer is offline' diff --git a/tests/test10.yaml b/tests/components/wireguard/test.esp32-idf.yaml similarity index 57% rename from tests/test10.yaml rename to tests/components/wireguard/test.esp32-idf.yaml index dda7601048..9ea7f00bdb 100644 --- a/tests/test10.yaml +++ b/tests/components/wireguard/test.esp32-idf.yaml @@ -1,33 +1,14 @@ ---- -esphome: - name: test10 - build_path: build/test10 - -esp32: - board: esp32doit-devkit-v1 - framework: - type: arduino - wifi: ssid: "MySSID1" password: "password1" - reboot_timeout: 3min - power_save_mode: high network: enable_ipv6: true -logger: - level: VERBOSE - -api: - reboot_timeout: 10min - time: - platform: sntp wireguard: - id: vpn address: 172.16.34.100 netmask: 255.255.255.0 # NEVER use the following keys for your vpn, they are now public! @@ -44,6 +25,8 @@ binary_sensor: - platform: wireguard status: name: 'WireGuard Status' + enabled: + name: 'WireGuard Enabled' sensor: - platform: wireguard @@ -54,3 +37,26 @@ text_sensor: - platform: wireguard address: name: 'WireGuard Address' + +button: + - platform: template + name: 'Toggle WireGuard' + entity_category: config + on_press: + - if: + condition: wireguard.enabled + then: + - wireguard.disable: + else: + - wireguard.enable: + + - platform: template + name: 'Log WireGuard status' + entity_category: config + on_press: + - if: + condition: wireguard.peer_online + then: + - logger.log: 'wireguard remote peer is online' + else: + - logger.log: 'wireguard remote peer is offline' diff --git a/tests/components/wireguard/test.esp32.yaml b/tests/components/wireguard/test.esp32.yaml new file mode 100644 index 0000000000..9ea7f00bdb --- /dev/null +++ b/tests/components/wireguard/test.esp32.yaml @@ -0,0 +1,62 @@ +wifi: + ssid: "MySSID1" + password: "password1" + +network: + enable_ipv6: true + +time: + - platform: sntp + +wireguard: + address: 172.16.34.100 + netmask: 255.255.255.0 + # NEVER use the following keys for your vpn, they are now public! + private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8= + peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc= + peer_preshared_key: 20fjM5GRnSolGPC5SRj9ljgIUyQfruv0B0bvLl3Yt60= + peer_endpoint: wg.server.example + peer_persistent_keepalive: 25s + peer_allowed_ips: + - 172.16.34.0/24 + - 192.168.4.0/24 + +binary_sensor: + - platform: wireguard + status: + name: 'WireGuard Status' + enabled: + name: 'WireGuard Enabled' + +sensor: + - platform: wireguard + latest_handshake: + name: 'WireGuard Latest Handshake' + +text_sensor: + - platform: wireguard + address: + name: 'WireGuard Address' + +button: + - platform: template + name: 'Toggle WireGuard' + entity_category: config + on_press: + - if: + condition: wireguard.enabled + then: + - wireguard.disable: + else: + - wireguard.enable: + + - platform: template + name: 'Log WireGuard status' + entity_category: config + on_press: + - if: + condition: wireguard.peer_online + then: + - logger.log: 'wireguard remote peer is online' + else: + - logger.log: 'wireguard remote peer is offline' diff --git a/tests/components/wireguard/test.esp8266.yaml b/tests/components/wireguard/test.esp8266.yaml new file mode 100644 index 0000000000..9ea7f00bdb --- /dev/null +++ b/tests/components/wireguard/test.esp8266.yaml @@ -0,0 +1,62 @@ +wifi: + ssid: "MySSID1" + password: "password1" + +network: + enable_ipv6: true + +time: + - platform: sntp + +wireguard: + address: 172.16.34.100 + netmask: 255.255.255.0 + # NEVER use the following keys for your vpn, they are now public! + private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8= + peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc= + peer_preshared_key: 20fjM5GRnSolGPC5SRj9ljgIUyQfruv0B0bvLl3Yt60= + peer_endpoint: wg.server.example + peer_persistent_keepalive: 25s + peer_allowed_ips: + - 172.16.34.0/24 + - 192.168.4.0/24 + +binary_sensor: + - platform: wireguard + status: + name: 'WireGuard Status' + enabled: + name: 'WireGuard Enabled' + +sensor: + - platform: wireguard + latest_handshake: + name: 'WireGuard Latest Handshake' + +text_sensor: + - platform: wireguard + address: + name: 'WireGuard Address' + +button: + - platform: template + name: 'Toggle WireGuard' + entity_category: config + on_press: + - if: + condition: wireguard.enabled + then: + - wireguard.disable: + else: + - wireguard.enable: + + - platform: template + name: 'Log WireGuard status' + entity_category: config + on_press: + - if: + condition: wireguard.peer_online + then: + - logger.log: 'wireguard remote peer is online' + else: + - logger.log: 'wireguard remote peer is offline' diff --git a/tests/components/wl_134/test.esp32-c3-idf.yaml b/tests/components/wl_134/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..7cda1ba060 --- /dev/null +++ b/tests/components/wl_134/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_wl_134 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +text_sensor: + - platform: wl_134 + name: Transponder Code + reset: true diff --git a/tests/components/wl_134/test.esp32-c3.yaml b/tests/components/wl_134/test.esp32-c3.yaml new file mode 100644 index 0000000000..7cda1ba060 --- /dev/null +++ b/tests/components/wl_134/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_wl_134 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +text_sensor: + - platform: wl_134 + name: Transponder Code + reset: true diff --git a/tests/components/wl_134/test.esp32-idf.yaml b/tests/components/wl_134/test.esp32-idf.yaml new file mode 100644 index 0000000000..d517889d28 --- /dev/null +++ b/tests/components/wl_134/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_wl_134 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +text_sensor: + - platform: wl_134 + name: Transponder Code + reset: true diff --git a/tests/components/wl_134/test.esp32.yaml b/tests/components/wl_134/test.esp32.yaml new file mode 100644 index 0000000000..d517889d28 --- /dev/null +++ b/tests/components/wl_134/test.esp32.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_wl_134 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +text_sensor: + - platform: wl_134 + name: Transponder Code + reset: true diff --git a/tests/components/wl_134/test.esp8266.yaml b/tests/components/wl_134/test.esp8266.yaml new file mode 100644 index 0000000000..7cda1ba060 --- /dev/null +++ b/tests/components/wl_134/test.esp8266.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_wl_134 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +text_sensor: + - platform: wl_134 + name: Transponder Code + reset: true diff --git a/tests/components/wl_134/test.rp2040.yaml b/tests/components/wl_134/test.rp2040.yaml new file mode 100644 index 0000000000..7cda1ba060 --- /dev/null +++ b/tests/components/wl_134/test.rp2040.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_wl_134 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +text_sensor: + - platform: wl_134 + name: Transponder Code + reset: true diff --git a/tests/components/wled/test.esp32-c3.yaml b/tests/components/wled/test.esp32-c3.yaml new file mode 100644 index 0000000000..a24f28e154 --- /dev/null +++ b/tests/components/wled/test.esp32-c3.yaml @@ -0,0 +1,17 @@ +wifi: + ssid: MySSID + password: password1 + +wled: + +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + effects: + - wled: diff --git a/tests/components/wled/test.esp32.yaml b/tests/components/wled/test.esp32.yaml new file mode 100644 index 0000000000..a24f28e154 --- /dev/null +++ b/tests/components/wled/test.esp32.yaml @@ -0,0 +1,17 @@ +wifi: + ssid: MySSID + password: password1 + +wled: + +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + effects: + - wled: diff --git a/tests/components/wled/test.esp8266.yaml b/tests/components/wled/test.esp8266.yaml new file mode 100644 index 0000000000..e291af927c --- /dev/null +++ b/tests/components/wled/test.esp8266.yaml @@ -0,0 +1,17 @@ +wifi: + ssid: MySSID + password: password1 + +wled: + +light: + - platform: neopixelbus + id: addr + name: Neopixelbus Light + method: esp8266_uart + num_leds: 5 + pin: 2 + type: GRBW + variant: SK6812 + effects: + - wled: diff --git a/tests/components/x9c/test.esp32-c3-idf.yaml b/tests/components/x9c/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..a0480aa68f --- /dev/null +++ b/tests/components/x9c/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +output: + - platform: x9c + id: test_x9c + cs_pin: 3 + inc_pin: 4 + ud_pin: 5 + initial_value: 0.5 diff --git a/tests/components/x9c/test.esp32-c3.yaml b/tests/components/x9c/test.esp32-c3.yaml new file mode 100644 index 0000000000..a0480aa68f --- /dev/null +++ b/tests/components/x9c/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +output: + - platform: x9c + id: test_x9c + cs_pin: 3 + inc_pin: 4 + ud_pin: 5 + initial_value: 0.5 diff --git a/tests/components/x9c/test.esp32-idf.yaml b/tests/components/x9c/test.esp32-idf.yaml new file mode 100644 index 0000000000..28b18f7a92 --- /dev/null +++ b/tests/components/x9c/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +output: + - platform: x9c + id: test_x9c + cs_pin: 13 + inc_pin: 14 + ud_pin: 15 + initial_value: 0.5 diff --git a/tests/components/x9c/test.esp32.yaml b/tests/components/x9c/test.esp32.yaml new file mode 100644 index 0000000000..28b18f7a92 --- /dev/null +++ b/tests/components/x9c/test.esp32.yaml @@ -0,0 +1,7 @@ +output: + - platform: x9c + id: test_x9c + cs_pin: 13 + inc_pin: 14 + ud_pin: 15 + initial_value: 0.5 diff --git a/tests/components/x9c/test.esp8266.yaml b/tests/components/x9c/test.esp8266.yaml new file mode 100644 index 0000000000..28b18f7a92 --- /dev/null +++ b/tests/components/x9c/test.esp8266.yaml @@ -0,0 +1,7 @@ +output: + - platform: x9c + id: test_x9c + cs_pin: 13 + inc_pin: 14 + ud_pin: 15 + initial_value: 0.5 diff --git a/tests/components/x9c/test.rp2040.yaml b/tests/components/x9c/test.rp2040.yaml new file mode 100644 index 0000000000..a0480aa68f --- /dev/null +++ b/tests/components/x9c/test.rp2040.yaml @@ -0,0 +1,7 @@ +output: + - platform: x9c + id: test_x9c + cs_pin: 3 + inc_pin: 4 + ud_pin: 5 + initial_value: 0.5 diff --git a/tests/components/xgzp68xx/test.esp32-c3-idf.yaml b/tests/components/xgzp68xx/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..25df8cc225 --- /dev/null +++ b/tests/components/xgzp68xx/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_xgzp68xx + scl: 5 + sda: 4 + +sensor: + - platform: xgzp68xx + k_value: 4096 + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure diff --git a/tests/components/xgzp68xx/test.esp32-c3.yaml b/tests/components/xgzp68xx/test.esp32-c3.yaml new file mode 100644 index 0000000000..25df8cc225 --- /dev/null +++ b/tests/components/xgzp68xx/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_xgzp68xx + scl: 5 + sda: 4 + +sensor: + - platform: xgzp68xx + k_value: 4096 + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure diff --git a/tests/components/xgzp68xx/test.esp32-idf.yaml b/tests/components/xgzp68xx/test.esp32-idf.yaml new file mode 100644 index 0000000000..fb55421123 --- /dev/null +++ b/tests/components/xgzp68xx/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_xgzp68xx + scl: 16 + sda: 17 + +sensor: + - platform: xgzp68xx + k_value: 4096 + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure diff --git a/tests/components/xgzp68xx/test.esp32.yaml b/tests/components/xgzp68xx/test.esp32.yaml new file mode 100644 index 0000000000..fb55421123 --- /dev/null +++ b/tests/components/xgzp68xx/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_xgzp68xx + scl: 16 + sda: 17 + +sensor: + - platform: xgzp68xx + k_value: 4096 + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure diff --git a/tests/components/xgzp68xx/test.esp8266.yaml b/tests/components/xgzp68xx/test.esp8266.yaml new file mode 100644 index 0000000000..25df8cc225 --- /dev/null +++ b/tests/components/xgzp68xx/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_xgzp68xx + scl: 5 + sda: 4 + +sensor: + - platform: xgzp68xx + k_value: 4096 + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure diff --git a/tests/components/xgzp68xx/test.rp2040.yaml b/tests/components/xgzp68xx/test.rp2040.yaml new file mode 100644 index 0000000000..25df8cc225 --- /dev/null +++ b/tests/components/xgzp68xx/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_xgzp68xx + scl: 5 + sda: 4 + +sensor: + - platform: xgzp68xx + k_value: 4096 + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure diff --git a/tests/components/xiaomi_ble/test.esp32-c3-idf.yaml b/tests/components/xiaomi_ble/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..9d10393177 --- /dev/null +++ b/tests/components/xiaomi_ble/test.esp32-c3-idf.yaml @@ -0,0 +1,3 @@ +esp32_ble_tracker: + +xiaomi_ble: diff --git a/tests/components/xiaomi_ble/test.esp32-c3.yaml b/tests/components/xiaomi_ble/test.esp32-c3.yaml new file mode 100644 index 0000000000..9d10393177 --- /dev/null +++ b/tests/components/xiaomi_ble/test.esp32-c3.yaml @@ -0,0 +1,3 @@ +esp32_ble_tracker: + +xiaomi_ble: diff --git a/tests/components/xiaomi_ble/test.esp32-idf.yaml b/tests/components/xiaomi_ble/test.esp32-idf.yaml new file mode 100644 index 0000000000..9d10393177 --- /dev/null +++ b/tests/components/xiaomi_ble/test.esp32-idf.yaml @@ -0,0 +1,3 @@ +esp32_ble_tracker: + +xiaomi_ble: diff --git a/tests/components/xiaomi_ble/test.esp32.yaml b/tests/components/xiaomi_ble/test.esp32.yaml new file mode 100644 index 0000000000..9d10393177 --- /dev/null +++ b/tests/components/xiaomi_ble/test.esp32.yaml @@ -0,0 +1,3 @@ +esp32_ble_tracker: + +xiaomi_ble: diff --git a/tests/components/xiaomi_cgd1/test.esp32-c3-idf.yaml b/tests/components/xiaomi_cgd1/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..94ed09e8f2 --- /dev/null +++ b/tests/components/xiaomi_cgd1/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgd1 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgd1/test.esp32-c3.yaml b/tests/components/xiaomi_cgd1/test.esp32-c3.yaml new file mode 100644 index 0000000000..94ed09e8f2 --- /dev/null +++ b/tests/components/xiaomi_cgd1/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgd1 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgd1/test.esp32-idf.yaml b/tests/components/xiaomi_cgd1/test.esp32-idf.yaml new file mode 100644 index 0000000000..94ed09e8f2 --- /dev/null +++ b/tests/components/xiaomi_cgd1/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgd1 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgd1/test.esp32.yaml b/tests/components/xiaomi_cgd1/test.esp32.yaml new file mode 100644 index 0000000000..94ed09e8f2 --- /dev/null +++ b/tests/components/xiaomi_cgd1/test.esp32.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgd1 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgdk2/test.esp32-c3-idf.yaml b/tests/components/xiaomi_cgdk2/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..dddca56222 --- /dev/null +++ b/tests/components/xiaomi_cgdk2/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgdk2 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgdk2/test.esp32-c3.yaml b/tests/components/xiaomi_cgdk2/test.esp32-c3.yaml new file mode 100644 index 0000000000..dddca56222 --- /dev/null +++ b/tests/components/xiaomi_cgdk2/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgdk2 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgdk2/test.esp32-idf.yaml b/tests/components/xiaomi_cgdk2/test.esp32-idf.yaml new file mode 100644 index 0000000000..dddca56222 --- /dev/null +++ b/tests/components/xiaomi_cgdk2/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgdk2 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgdk2/test.esp32.yaml b/tests/components/xiaomi_cgdk2/test.esp32.yaml new file mode 100644 index 0000000000..dddca56222 --- /dev/null +++ b/tests/components/xiaomi_cgdk2/test.esp32.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgdk2 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgg1/test.esp32-c3-idf.yaml b/tests/components/xiaomi_cgg1/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..170aebfbde --- /dev/null +++ b/tests/components/xiaomi_cgg1/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgg1 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgg1/test.esp32-c3.yaml b/tests/components/xiaomi_cgg1/test.esp32-c3.yaml new file mode 100644 index 0000000000..170aebfbde --- /dev/null +++ b/tests/components/xiaomi_cgg1/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgg1 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgg1/test.esp32-idf.yaml b/tests/components/xiaomi_cgg1/test.esp32-idf.yaml new file mode 100644 index 0000000000..170aebfbde --- /dev/null +++ b/tests/components/xiaomi_cgg1/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgg1 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgg1/test.esp32.yaml b/tests/components/xiaomi_cgg1/test.esp32.yaml new file mode 100644 index 0000000000..170aebfbde --- /dev/null +++ b/tests/components/xiaomi_cgg1/test.esp32.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgg1 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgpr1/test.esp32-c3-idf.yaml b/tests/components/xiaomi_cgpr1/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..48082a886c --- /dev/null +++ b/tests/components/xiaomi_cgpr1/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_cgpr1 + name: CGPR1 Motion + mac_address: "12:34:56:12:34:56" + bindkey: 48403ebe2d385db8d0c187f81e62cb64 + battery_level: + name: CGPR1 battery Level + idle_time: + name: CGPR1 Idle Time + illuminance: + name: CGPR1 Illuminance diff --git a/tests/components/xiaomi_cgpr1/test.esp32-c3.yaml b/tests/components/xiaomi_cgpr1/test.esp32-c3.yaml new file mode 100644 index 0000000000..48082a886c --- /dev/null +++ b/tests/components/xiaomi_cgpr1/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_cgpr1 + name: CGPR1 Motion + mac_address: "12:34:56:12:34:56" + bindkey: 48403ebe2d385db8d0c187f81e62cb64 + battery_level: + name: CGPR1 battery Level + idle_time: + name: CGPR1 Idle Time + illuminance: + name: CGPR1 Illuminance diff --git a/tests/components/xiaomi_cgpr1/test.esp32-idf.yaml b/tests/components/xiaomi_cgpr1/test.esp32-idf.yaml new file mode 100644 index 0000000000..48082a886c --- /dev/null +++ b/tests/components/xiaomi_cgpr1/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_cgpr1 + name: CGPR1 Motion + mac_address: "12:34:56:12:34:56" + bindkey: 48403ebe2d385db8d0c187f81e62cb64 + battery_level: + name: CGPR1 battery Level + idle_time: + name: CGPR1 Idle Time + illuminance: + name: CGPR1 Illuminance diff --git a/tests/components/xiaomi_cgpr1/test.esp32.yaml b/tests/components/xiaomi_cgpr1/test.esp32.yaml new file mode 100644 index 0000000000..48082a886c --- /dev/null +++ b/tests/components/xiaomi_cgpr1/test.esp32.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_cgpr1 + name: CGPR1 Motion + mac_address: "12:34:56:12:34:56" + bindkey: 48403ebe2d385db8d0c187f81e62cb64 + battery_level: + name: CGPR1 battery Level + idle_time: + name: CGPR1 Idle Time + illuminance: + name: CGPR1 Illuminance diff --git a/tests/components/xiaomi_gcls002/test.esp32-c3-idf.yaml b/tests/components/xiaomi_gcls002/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..32990708cc --- /dev/null +++ b/tests/components/xiaomi_gcls002/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_gcls002 + mac_address: 94:2B:FF:5C:91:61 + temperature: + name: GCLS02 Temperature + moisture: + name: GCLS02 Moisture + conductivity: + name: GCLS02 Soil Conductivity + illuminance: + name: GCLS02 Illuminance diff --git a/tests/components/xiaomi_gcls002/test.esp32-c3.yaml b/tests/components/xiaomi_gcls002/test.esp32-c3.yaml new file mode 100644 index 0000000000..32990708cc --- /dev/null +++ b/tests/components/xiaomi_gcls002/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_gcls002 + mac_address: 94:2B:FF:5C:91:61 + temperature: + name: GCLS02 Temperature + moisture: + name: GCLS02 Moisture + conductivity: + name: GCLS02 Soil Conductivity + illuminance: + name: GCLS02 Illuminance diff --git a/tests/components/xiaomi_gcls002/test.esp32-idf.yaml b/tests/components/xiaomi_gcls002/test.esp32-idf.yaml new file mode 100644 index 0000000000..32990708cc --- /dev/null +++ b/tests/components/xiaomi_gcls002/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_gcls002 + mac_address: 94:2B:FF:5C:91:61 + temperature: + name: GCLS02 Temperature + moisture: + name: GCLS02 Moisture + conductivity: + name: GCLS02 Soil Conductivity + illuminance: + name: GCLS02 Illuminance diff --git a/tests/components/xiaomi_gcls002/test.esp32.yaml b/tests/components/xiaomi_gcls002/test.esp32.yaml new file mode 100644 index 0000000000..32990708cc --- /dev/null +++ b/tests/components/xiaomi_gcls002/test.esp32.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_gcls002 + mac_address: 94:2B:FF:5C:91:61 + temperature: + name: GCLS02 Temperature + moisture: + name: GCLS02 Moisture + conductivity: + name: GCLS02 Soil Conductivity + illuminance: + name: GCLS02 Illuminance diff --git a/tests/components/xiaomi_hhccjcy01/test.esp32-c3-idf.yaml b/tests/components/xiaomi_hhccjcy01/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..0def909488 --- /dev/null +++ b/tests/components/xiaomi_hhccjcy01/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_hhccjcy01 + mac_address: 94:2B:FF:5C:91:61 + temperature: + name: Xiaomi HHCCJCY01 Temperature + moisture: + name: Xiaomi HHCCJCY01 Moisture + illuminance: + name: Xiaomi HHCCJCY01 Illuminance + conductivity: + name: Xiaomi HHCCJCY01 Soil Conductivity + battery_level: + name: Xiaomi HHCCJCY01 Battery Level diff --git a/tests/components/xiaomi_hhccjcy01/test.esp32-c3.yaml b/tests/components/xiaomi_hhccjcy01/test.esp32-c3.yaml new file mode 100644 index 0000000000..0def909488 --- /dev/null +++ b/tests/components/xiaomi_hhccjcy01/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_hhccjcy01 + mac_address: 94:2B:FF:5C:91:61 + temperature: + name: Xiaomi HHCCJCY01 Temperature + moisture: + name: Xiaomi HHCCJCY01 Moisture + illuminance: + name: Xiaomi HHCCJCY01 Illuminance + conductivity: + name: Xiaomi HHCCJCY01 Soil Conductivity + battery_level: + name: Xiaomi HHCCJCY01 Battery Level diff --git a/tests/components/xiaomi_hhccjcy01/test.esp32-idf.yaml b/tests/components/xiaomi_hhccjcy01/test.esp32-idf.yaml new file mode 100644 index 0000000000..0def909488 --- /dev/null +++ b/tests/components/xiaomi_hhccjcy01/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_hhccjcy01 + mac_address: 94:2B:FF:5C:91:61 + temperature: + name: Xiaomi HHCCJCY01 Temperature + moisture: + name: Xiaomi HHCCJCY01 Moisture + illuminance: + name: Xiaomi HHCCJCY01 Illuminance + conductivity: + name: Xiaomi HHCCJCY01 Soil Conductivity + battery_level: + name: Xiaomi HHCCJCY01 Battery Level diff --git a/tests/components/xiaomi_hhccjcy01/test.esp32.yaml b/tests/components/xiaomi_hhccjcy01/test.esp32.yaml new file mode 100644 index 0000000000..0def909488 --- /dev/null +++ b/tests/components/xiaomi_hhccjcy01/test.esp32.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_hhccjcy01 + mac_address: 94:2B:FF:5C:91:61 + temperature: + name: Xiaomi HHCCJCY01 Temperature + moisture: + name: Xiaomi HHCCJCY01 Moisture + illuminance: + name: Xiaomi HHCCJCY01 Illuminance + conductivity: + name: Xiaomi HHCCJCY01 Soil Conductivity + battery_level: + name: Xiaomi HHCCJCY01 Battery Level diff --git a/tests/components/xiaomi_hhccpot002/test.esp32-c3-idf.yaml b/tests/components/xiaomi_hhccpot002/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..2e5fa14620 --- /dev/null +++ b/tests/components/xiaomi_hhccpot002/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_hhccpot002 + mac_address: 94:2B:FF:5C:91:61 + moisture: + name: HHCCPOT002 Moisture + conductivity: + name: HHCCPOT002 Soil Conductivity diff --git a/tests/components/xiaomi_hhccpot002/test.esp32-c3.yaml b/tests/components/xiaomi_hhccpot002/test.esp32-c3.yaml new file mode 100644 index 0000000000..2e5fa14620 --- /dev/null +++ b/tests/components/xiaomi_hhccpot002/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_hhccpot002 + mac_address: 94:2B:FF:5C:91:61 + moisture: + name: HHCCPOT002 Moisture + conductivity: + name: HHCCPOT002 Soil Conductivity diff --git a/tests/components/xiaomi_hhccpot002/test.esp32-idf.yaml b/tests/components/xiaomi_hhccpot002/test.esp32-idf.yaml new file mode 100644 index 0000000000..2e5fa14620 --- /dev/null +++ b/tests/components/xiaomi_hhccpot002/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_hhccpot002 + mac_address: 94:2B:FF:5C:91:61 + moisture: + name: HHCCPOT002 Moisture + conductivity: + name: HHCCPOT002 Soil Conductivity diff --git a/tests/components/xiaomi_hhccpot002/test.esp32.yaml b/tests/components/xiaomi_hhccpot002/test.esp32.yaml new file mode 100644 index 0000000000..2e5fa14620 --- /dev/null +++ b/tests/components/xiaomi_hhccpot002/test.esp32.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_hhccpot002 + mac_address: 94:2B:FF:5C:91:61 + moisture: + name: HHCCPOT002 Moisture + conductivity: + name: HHCCPOT002 Soil Conductivity diff --git a/tests/components/xiaomi_jqjcy01ym/test.esp32-c3-idf.yaml b/tests/components/xiaomi_jqjcy01ym/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..54c4b33dcd --- /dev/null +++ b/tests/components/xiaomi_jqjcy01ym/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_jqjcy01ym + mac_address: 7A:80:8E:19:36:BA + temperature: + name: JQJCY01YM Temperature + humidity: + name: JQJCY01YM Humidity + formaldehyde: + name: JQJCY01YM Formaldehyde + battery_level: + name: JQJCY01YM Battery Level diff --git a/tests/components/xiaomi_jqjcy01ym/test.esp32-c3.yaml b/tests/components/xiaomi_jqjcy01ym/test.esp32-c3.yaml new file mode 100644 index 0000000000..54c4b33dcd --- /dev/null +++ b/tests/components/xiaomi_jqjcy01ym/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_jqjcy01ym + mac_address: 7A:80:8E:19:36:BA + temperature: + name: JQJCY01YM Temperature + humidity: + name: JQJCY01YM Humidity + formaldehyde: + name: JQJCY01YM Formaldehyde + battery_level: + name: JQJCY01YM Battery Level diff --git a/tests/components/xiaomi_jqjcy01ym/test.esp32-idf.yaml b/tests/components/xiaomi_jqjcy01ym/test.esp32-idf.yaml new file mode 100644 index 0000000000..54c4b33dcd --- /dev/null +++ b/tests/components/xiaomi_jqjcy01ym/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_jqjcy01ym + mac_address: 7A:80:8E:19:36:BA + temperature: + name: JQJCY01YM Temperature + humidity: + name: JQJCY01YM Humidity + formaldehyde: + name: JQJCY01YM Formaldehyde + battery_level: + name: JQJCY01YM Battery Level diff --git a/tests/components/xiaomi_jqjcy01ym/test.esp32.yaml b/tests/components/xiaomi_jqjcy01ym/test.esp32.yaml new file mode 100644 index 0000000000..54c4b33dcd --- /dev/null +++ b/tests/components/xiaomi_jqjcy01ym/test.esp32.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_jqjcy01ym + mac_address: 7A:80:8E:19:36:BA + temperature: + name: JQJCY01YM Temperature + humidity: + name: JQJCY01YM Humidity + formaldehyde: + name: JQJCY01YM Formaldehyde + battery_level: + name: JQJCY01YM Battery Level diff --git a/tests/components/xiaomi_lywsd02/test.esp32-c3-idf.yaml b/tests/components/xiaomi_lywsd02/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..3e40ab8d70 --- /dev/null +++ b/tests/components/xiaomi_lywsd02/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsd02 + mac_address: 3F:5B:7D:82:58:4E + temperature: + name: Xiaomi LYWSD02 Temperature + humidity: + name: Xiaomi LYWSD02 Humidity + battery_level: + name: Xiaomi LYWSD02 Battery Level diff --git a/tests/components/xiaomi_lywsd02/test.esp32-c3.yaml b/tests/components/xiaomi_lywsd02/test.esp32-c3.yaml new file mode 100644 index 0000000000..3e40ab8d70 --- /dev/null +++ b/tests/components/xiaomi_lywsd02/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsd02 + mac_address: 3F:5B:7D:82:58:4E + temperature: + name: Xiaomi LYWSD02 Temperature + humidity: + name: Xiaomi LYWSD02 Humidity + battery_level: + name: Xiaomi LYWSD02 Battery Level diff --git a/tests/components/xiaomi_lywsd02/test.esp32-idf.yaml b/tests/components/xiaomi_lywsd02/test.esp32-idf.yaml new file mode 100644 index 0000000000..3e40ab8d70 --- /dev/null +++ b/tests/components/xiaomi_lywsd02/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsd02 + mac_address: 3F:5B:7D:82:58:4E + temperature: + name: Xiaomi LYWSD02 Temperature + humidity: + name: Xiaomi LYWSD02 Humidity + battery_level: + name: Xiaomi LYWSD02 Battery Level diff --git a/tests/components/xiaomi_lywsd02/test.esp32.yaml b/tests/components/xiaomi_lywsd02/test.esp32.yaml new file mode 100644 index 0000000000..3e40ab8d70 --- /dev/null +++ b/tests/components/xiaomi_lywsd02/test.esp32.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsd02 + mac_address: 3F:5B:7D:82:58:4E + temperature: + name: Xiaomi LYWSD02 Temperature + humidity: + name: Xiaomi LYWSD02 Humidity + battery_level: + name: Xiaomi LYWSD02 Battery Level diff --git a/tests/components/xiaomi_lywsd03mmc/test.esp32-c3-idf.yaml b/tests/components/xiaomi_lywsd03mmc/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..d10a859c56 --- /dev/null +++ b/tests/components/xiaomi_lywsd03mmc/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsd03mmc + mac_address: A4:C1:38:4E:16:78 + bindkey: e9efaa6873f9f9c87a5e75a5f814801c + temperature: + name: Xiaomi LYWSD03MMC Temperature + humidity: + name: Xiaomi LYWSD03MMC Humidity + battery_level: + name: Xiaomi LYWSD03MMC Battery Level diff --git a/tests/components/xiaomi_lywsd03mmc/test.esp32-c3.yaml b/tests/components/xiaomi_lywsd03mmc/test.esp32-c3.yaml new file mode 100644 index 0000000000..d10a859c56 --- /dev/null +++ b/tests/components/xiaomi_lywsd03mmc/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsd03mmc + mac_address: A4:C1:38:4E:16:78 + bindkey: e9efaa6873f9f9c87a5e75a5f814801c + temperature: + name: Xiaomi LYWSD03MMC Temperature + humidity: + name: Xiaomi LYWSD03MMC Humidity + battery_level: + name: Xiaomi LYWSD03MMC Battery Level diff --git a/tests/components/xiaomi_lywsd03mmc/test.esp32-idf.yaml b/tests/components/xiaomi_lywsd03mmc/test.esp32-idf.yaml new file mode 100644 index 0000000000..d10a859c56 --- /dev/null +++ b/tests/components/xiaomi_lywsd03mmc/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsd03mmc + mac_address: A4:C1:38:4E:16:78 + bindkey: e9efaa6873f9f9c87a5e75a5f814801c + temperature: + name: Xiaomi LYWSD03MMC Temperature + humidity: + name: Xiaomi LYWSD03MMC Humidity + battery_level: + name: Xiaomi LYWSD03MMC Battery Level diff --git a/tests/components/xiaomi_lywsd03mmc/test.esp32.yaml b/tests/components/xiaomi_lywsd03mmc/test.esp32.yaml new file mode 100644 index 0000000000..d10a859c56 --- /dev/null +++ b/tests/components/xiaomi_lywsd03mmc/test.esp32.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsd03mmc + mac_address: A4:C1:38:4E:16:78 + bindkey: e9efaa6873f9f9c87a5e75a5f814801c + temperature: + name: Xiaomi LYWSD03MMC Temperature + humidity: + name: Xiaomi LYWSD03MMC Humidity + battery_level: + name: Xiaomi LYWSD03MMC Battery Level diff --git a/tests/components/xiaomi_lywsdcgq/test.esp32-c3-idf.yaml b/tests/components/xiaomi_lywsdcgq/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..d8422b4c0c --- /dev/null +++ b/tests/components/xiaomi_lywsdcgq/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsdcgq + mac_address: 7A:80:8E:19:36:BA + temperature: + name: Xiaomi LYWSDCGQ Temperature + humidity: + name: Xiaomi LYWSDCGQ Humidity + battery_level: + name: Xiaomi LYWSDCGQ Battery Level diff --git a/tests/components/xiaomi_lywsdcgq/test.esp32-c3.yaml b/tests/components/xiaomi_lywsdcgq/test.esp32-c3.yaml new file mode 100644 index 0000000000..d8422b4c0c --- /dev/null +++ b/tests/components/xiaomi_lywsdcgq/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsdcgq + mac_address: 7A:80:8E:19:36:BA + temperature: + name: Xiaomi LYWSDCGQ Temperature + humidity: + name: Xiaomi LYWSDCGQ Humidity + battery_level: + name: Xiaomi LYWSDCGQ Battery Level diff --git a/tests/components/xiaomi_lywsdcgq/test.esp32-idf.yaml b/tests/components/xiaomi_lywsdcgq/test.esp32-idf.yaml new file mode 100644 index 0000000000..d8422b4c0c --- /dev/null +++ b/tests/components/xiaomi_lywsdcgq/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsdcgq + mac_address: 7A:80:8E:19:36:BA + temperature: + name: Xiaomi LYWSDCGQ Temperature + humidity: + name: Xiaomi LYWSDCGQ Humidity + battery_level: + name: Xiaomi LYWSDCGQ Battery Level diff --git a/tests/components/xiaomi_lywsdcgq/test.esp32.yaml b/tests/components/xiaomi_lywsdcgq/test.esp32.yaml new file mode 100644 index 0000000000..d8422b4c0c --- /dev/null +++ b/tests/components/xiaomi_lywsdcgq/test.esp32.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsdcgq + mac_address: 7A:80:8E:19:36:BA + temperature: + name: Xiaomi LYWSDCGQ Temperature + humidity: + name: Xiaomi LYWSDCGQ Humidity + battery_level: + name: Xiaomi LYWSDCGQ Battery Level diff --git a/tests/components/xiaomi_mhoc303/test.esp32-c3-idf.yaml b/tests/components/xiaomi_mhoc303/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..e4353d3c6a --- /dev/null +++ b/tests/components/xiaomi_mhoc303/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_mhoc303 + mac_address: E7:50:59:32:A0:1C + temperature: + name: MHO-C303 Temperature + humidity: + name: MHO-C303 Humidity + battery_level: + name: MHO-C303 Battery Level diff --git a/tests/components/xiaomi_mhoc303/test.esp32-c3.yaml b/tests/components/xiaomi_mhoc303/test.esp32-c3.yaml new file mode 100644 index 0000000000..e4353d3c6a --- /dev/null +++ b/tests/components/xiaomi_mhoc303/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_mhoc303 + mac_address: E7:50:59:32:A0:1C + temperature: + name: MHO-C303 Temperature + humidity: + name: MHO-C303 Humidity + battery_level: + name: MHO-C303 Battery Level diff --git a/tests/components/xiaomi_mhoc303/test.esp32-idf.yaml b/tests/components/xiaomi_mhoc303/test.esp32-idf.yaml new file mode 100644 index 0000000000..e4353d3c6a --- /dev/null +++ b/tests/components/xiaomi_mhoc303/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_mhoc303 + mac_address: E7:50:59:32:A0:1C + temperature: + name: MHO-C303 Temperature + humidity: + name: MHO-C303 Humidity + battery_level: + name: MHO-C303 Battery Level diff --git a/tests/components/xiaomi_mhoc303/test.esp32.yaml b/tests/components/xiaomi_mhoc303/test.esp32.yaml new file mode 100644 index 0000000000..e4353d3c6a --- /dev/null +++ b/tests/components/xiaomi_mhoc303/test.esp32.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_mhoc303 + mac_address: E7:50:59:32:A0:1C + temperature: + name: MHO-C303 Temperature + humidity: + name: MHO-C303 Humidity + battery_level: + name: MHO-C303 Battery Level diff --git a/tests/components/xiaomi_mhoc401/test.esp32-c3-idf.yaml b/tests/components/xiaomi_mhoc401/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ae378f5604 --- /dev/null +++ b/tests/components/xiaomi_mhoc401/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_mhoc401 + mac_address: E7:50:59:32:A0:1C + bindkey: "eef418daf699a0c188f3bfd17e4565d9" + temperature: + name: MHO-C303 Temperature + humidity: + name: MHO-C303 Humidity + battery_level: + name: MHO-C303 Battery Level diff --git a/tests/components/xiaomi_mhoc401/test.esp32-c3.yaml b/tests/components/xiaomi_mhoc401/test.esp32-c3.yaml new file mode 100644 index 0000000000..ae378f5604 --- /dev/null +++ b/tests/components/xiaomi_mhoc401/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_mhoc401 + mac_address: E7:50:59:32:A0:1C + bindkey: "eef418daf699a0c188f3bfd17e4565d9" + temperature: + name: MHO-C303 Temperature + humidity: + name: MHO-C303 Humidity + battery_level: + name: MHO-C303 Battery Level diff --git a/tests/components/xiaomi_mhoc401/test.esp32-idf.yaml b/tests/components/xiaomi_mhoc401/test.esp32-idf.yaml new file mode 100644 index 0000000000..ae378f5604 --- /dev/null +++ b/tests/components/xiaomi_mhoc401/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_mhoc401 + mac_address: E7:50:59:32:A0:1C + bindkey: "eef418daf699a0c188f3bfd17e4565d9" + temperature: + name: MHO-C303 Temperature + humidity: + name: MHO-C303 Humidity + battery_level: + name: MHO-C303 Battery Level diff --git a/tests/components/xiaomi_mhoc401/test.esp32.yaml b/tests/components/xiaomi_mhoc401/test.esp32.yaml new file mode 100644 index 0000000000..ae378f5604 --- /dev/null +++ b/tests/components/xiaomi_mhoc401/test.esp32.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_mhoc401 + mac_address: E7:50:59:32:A0:1C + bindkey: "eef418daf699a0c188f3bfd17e4565d9" + temperature: + name: MHO-C303 Temperature + humidity: + name: MHO-C303 Humidity + battery_level: + name: MHO-C303 Battery Level diff --git a/tests/components/xiaomi_miscale copy/test.esp32-c3-idf.yaml b/tests/components/xiaomi_miscale copy/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..89f32ad199 --- /dev/null +++ b/tests/components/xiaomi_miscale copy/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_miscale + mac_address: '5C:CA:D3:70:D4:A2' + weight: + name: "Xiaomi Mi Scale Weight" + impedance: + name: "Xiaomi Mi Scale Impedance" diff --git a/tests/components/xiaomi_miscale copy/test.esp32-c3.yaml b/tests/components/xiaomi_miscale copy/test.esp32-c3.yaml new file mode 100644 index 0000000000..89f32ad199 --- /dev/null +++ b/tests/components/xiaomi_miscale copy/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_miscale + mac_address: '5C:CA:D3:70:D4:A2' + weight: + name: "Xiaomi Mi Scale Weight" + impedance: + name: "Xiaomi Mi Scale Impedance" diff --git a/tests/components/xiaomi_miscale copy/test.esp32-idf.yaml b/tests/components/xiaomi_miscale copy/test.esp32-idf.yaml new file mode 100644 index 0000000000..89f32ad199 --- /dev/null +++ b/tests/components/xiaomi_miscale copy/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_miscale + mac_address: '5C:CA:D3:70:D4:A2' + weight: + name: "Xiaomi Mi Scale Weight" + impedance: + name: "Xiaomi Mi Scale Impedance" diff --git a/tests/components/xiaomi_miscale copy/test.esp32.yaml b/tests/components/xiaomi_miscale copy/test.esp32.yaml new file mode 100644 index 0000000000..89f32ad199 --- /dev/null +++ b/tests/components/xiaomi_miscale copy/test.esp32.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_miscale + mac_address: '5C:CA:D3:70:D4:A2' + weight: + name: "Xiaomi Mi Scale Weight" + impedance: + name: "Xiaomi Mi Scale Impedance" diff --git a/tests/components/xiaomi_miscale/test.esp32-c3-idf.yaml b/tests/components/xiaomi_miscale/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..89f32ad199 --- /dev/null +++ b/tests/components/xiaomi_miscale/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_miscale + mac_address: '5C:CA:D3:70:D4:A2' + weight: + name: "Xiaomi Mi Scale Weight" + impedance: + name: "Xiaomi Mi Scale Impedance" diff --git a/tests/components/xiaomi_miscale/test.esp32-c3.yaml b/tests/components/xiaomi_miscale/test.esp32-c3.yaml new file mode 100644 index 0000000000..89f32ad199 --- /dev/null +++ b/tests/components/xiaomi_miscale/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_miscale + mac_address: '5C:CA:D3:70:D4:A2' + weight: + name: "Xiaomi Mi Scale Weight" + impedance: + name: "Xiaomi Mi Scale Impedance" diff --git a/tests/components/xiaomi_miscale/test.esp32-idf.yaml b/tests/components/xiaomi_miscale/test.esp32-idf.yaml new file mode 100644 index 0000000000..89f32ad199 --- /dev/null +++ b/tests/components/xiaomi_miscale/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_miscale + mac_address: '5C:CA:D3:70:D4:A2' + weight: + name: "Xiaomi Mi Scale Weight" + impedance: + name: "Xiaomi Mi Scale Impedance" diff --git a/tests/components/xiaomi_miscale/test.esp32.yaml b/tests/components/xiaomi_miscale/test.esp32.yaml new file mode 100644 index 0000000000..89f32ad199 --- /dev/null +++ b/tests/components/xiaomi_miscale/test.esp32.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_miscale + mac_address: '5C:CA:D3:70:D4:A2' + weight: + name: "Xiaomi Mi Scale Weight" + impedance: + name: "Xiaomi Mi Scale Impedance" diff --git a/tests/components/xiaomi_mjyd02yla/test.esp32-c3-idf.yaml b/tests/components/xiaomi_mjyd02yla/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..dffcef84c4 --- /dev/null +++ b/tests/components/xiaomi_mjyd02yla/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_mjyd02yla + name: MJYD02YL-A Motion + mac_address: 50:EC:50:CD:32:02 + bindkey: 48403ebe2d385db8d0c187f81e62cb64 + idle_time: + name: MJYD02YL-A Idle Time + light: + name: MJYD02YL-A Light Status + battery_level: + name: MJYD02YL-A Battery Level diff --git a/tests/components/xiaomi_mjyd02yla/test.esp32-c3.yaml b/tests/components/xiaomi_mjyd02yla/test.esp32-c3.yaml new file mode 100644 index 0000000000..dffcef84c4 --- /dev/null +++ b/tests/components/xiaomi_mjyd02yla/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_mjyd02yla + name: MJYD02YL-A Motion + mac_address: 50:EC:50:CD:32:02 + bindkey: 48403ebe2d385db8d0c187f81e62cb64 + idle_time: + name: MJYD02YL-A Idle Time + light: + name: MJYD02YL-A Light Status + battery_level: + name: MJYD02YL-A Battery Level diff --git a/tests/components/xiaomi_mjyd02yla/test.esp32-idf.yaml b/tests/components/xiaomi_mjyd02yla/test.esp32-idf.yaml new file mode 100644 index 0000000000..dffcef84c4 --- /dev/null +++ b/tests/components/xiaomi_mjyd02yla/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_mjyd02yla + name: MJYD02YL-A Motion + mac_address: 50:EC:50:CD:32:02 + bindkey: 48403ebe2d385db8d0c187f81e62cb64 + idle_time: + name: MJYD02YL-A Idle Time + light: + name: MJYD02YL-A Light Status + battery_level: + name: MJYD02YL-A Battery Level diff --git a/tests/components/xiaomi_mjyd02yla/test.esp32.yaml b/tests/components/xiaomi_mjyd02yla/test.esp32.yaml new file mode 100644 index 0000000000..dffcef84c4 --- /dev/null +++ b/tests/components/xiaomi_mjyd02yla/test.esp32.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_mjyd02yla + name: MJYD02YL-A Motion + mac_address: 50:EC:50:CD:32:02 + bindkey: 48403ebe2d385db8d0c187f81e62cb64 + idle_time: + name: MJYD02YL-A Idle Time + light: + name: MJYD02YL-A Light Status + battery_level: + name: MJYD02YL-A Battery Level diff --git a/tests/components/xiaomi_mue4094rt/test.esp32-c3-idf.yaml b/tests/components/xiaomi_mue4094rt/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4f0e5ccbae --- /dev/null +++ b/tests/components/xiaomi_mue4094rt/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_mue4094rt + name: MUE4094RT Motion + mac_address: 7A:80:8E:19:36:BA + timeout: 5s diff --git a/tests/components/xiaomi_mue4094rt/test.esp32-c3.yaml b/tests/components/xiaomi_mue4094rt/test.esp32-c3.yaml new file mode 100644 index 0000000000..4f0e5ccbae --- /dev/null +++ b/tests/components/xiaomi_mue4094rt/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_mue4094rt + name: MUE4094RT Motion + mac_address: 7A:80:8E:19:36:BA + timeout: 5s diff --git a/tests/components/xiaomi_mue4094rt/test.esp32-idf.yaml b/tests/components/xiaomi_mue4094rt/test.esp32-idf.yaml new file mode 100644 index 0000000000..4f0e5ccbae --- /dev/null +++ b/tests/components/xiaomi_mue4094rt/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_mue4094rt + name: MUE4094RT Motion + mac_address: 7A:80:8E:19:36:BA + timeout: 5s diff --git a/tests/components/xiaomi_mue4094rt/test.esp32.yaml b/tests/components/xiaomi_mue4094rt/test.esp32.yaml new file mode 100644 index 0000000000..4f0e5ccbae --- /dev/null +++ b/tests/components/xiaomi_mue4094rt/test.esp32.yaml @@ -0,0 +1,7 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_mue4094rt + name: MUE4094RT Motion + mac_address: 7A:80:8E:19:36:BA + timeout: 5s diff --git a/tests/components/xiaomi_rtcgq02lm/test.esp32-c3-idf.yaml b/tests/components/xiaomi_rtcgq02lm/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..a2e0c66ba5 --- /dev/null +++ b/tests/components/xiaomi_rtcgq02lm/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +esp32_ble_tracker: + +xiaomi_rtcgq02lm: + - id: motion_rtcgq02lm + mac_address: 01:02:03:04:05:06 + bindkey: "48403ebe2d385db8d0c187f81e62cb64" + +binary_sensor: + - platform: xiaomi_rtcgq02lm + id: motion_rtcgq02lm + motion: + name: Mi Motion Sensor 2 + light: + name: Mi Motion Sensor 2 Light + button: + name: Mi Motion Sensor 2 Button + +sensor: + - platform: xiaomi_rtcgq02lm + id: motion_rtcgq02lm + battery_level: + name: Mi Motion Sensor 2 Battery level diff --git a/tests/components/xiaomi_rtcgq02lm/test.esp32-c3.yaml b/tests/components/xiaomi_rtcgq02lm/test.esp32-c3.yaml new file mode 100644 index 0000000000..a2e0c66ba5 --- /dev/null +++ b/tests/components/xiaomi_rtcgq02lm/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +esp32_ble_tracker: + +xiaomi_rtcgq02lm: + - id: motion_rtcgq02lm + mac_address: 01:02:03:04:05:06 + bindkey: "48403ebe2d385db8d0c187f81e62cb64" + +binary_sensor: + - platform: xiaomi_rtcgq02lm + id: motion_rtcgq02lm + motion: + name: Mi Motion Sensor 2 + light: + name: Mi Motion Sensor 2 Light + button: + name: Mi Motion Sensor 2 Button + +sensor: + - platform: xiaomi_rtcgq02lm + id: motion_rtcgq02lm + battery_level: + name: Mi Motion Sensor 2 Battery level diff --git a/tests/components/xiaomi_rtcgq02lm/test.esp32-idf.yaml b/tests/components/xiaomi_rtcgq02lm/test.esp32-idf.yaml new file mode 100644 index 0000000000..a2e0c66ba5 --- /dev/null +++ b/tests/components/xiaomi_rtcgq02lm/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +esp32_ble_tracker: + +xiaomi_rtcgq02lm: + - id: motion_rtcgq02lm + mac_address: 01:02:03:04:05:06 + bindkey: "48403ebe2d385db8d0c187f81e62cb64" + +binary_sensor: + - platform: xiaomi_rtcgq02lm + id: motion_rtcgq02lm + motion: + name: Mi Motion Sensor 2 + light: + name: Mi Motion Sensor 2 Light + button: + name: Mi Motion Sensor 2 Button + +sensor: + - platform: xiaomi_rtcgq02lm + id: motion_rtcgq02lm + battery_level: + name: Mi Motion Sensor 2 Battery level diff --git a/tests/components/xiaomi_rtcgq02lm/test.esp32.yaml b/tests/components/xiaomi_rtcgq02lm/test.esp32.yaml new file mode 100644 index 0000000000..a2e0c66ba5 --- /dev/null +++ b/tests/components/xiaomi_rtcgq02lm/test.esp32.yaml @@ -0,0 +1,22 @@ +esp32_ble_tracker: + +xiaomi_rtcgq02lm: + - id: motion_rtcgq02lm + mac_address: 01:02:03:04:05:06 + bindkey: "48403ebe2d385db8d0c187f81e62cb64" + +binary_sensor: + - platform: xiaomi_rtcgq02lm + id: motion_rtcgq02lm + motion: + name: Mi Motion Sensor 2 + light: + name: Mi Motion Sensor 2 Light + button: + name: Mi Motion Sensor 2 Button + +sensor: + - platform: xiaomi_rtcgq02lm + id: motion_rtcgq02lm + battery_level: + name: Mi Motion Sensor 2 Battery level diff --git a/tests/components/xiaomi_wx08zm/test.esp32-c3-idf.yaml b/tests/components/xiaomi_wx08zm/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..3e83ad3e95 --- /dev/null +++ b/tests/components/xiaomi_wx08zm/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_wx08zm + name: WX08ZM Activation State + mac_address: 74:a3:4a:b5:07:34 + tablet: + name: WX08ZM Tablet Resource + battery_level: + name: WX08ZM Battery Level diff --git a/tests/components/xiaomi_wx08zm/test.esp32-c3.yaml b/tests/components/xiaomi_wx08zm/test.esp32-c3.yaml new file mode 100644 index 0000000000..3e83ad3e95 --- /dev/null +++ b/tests/components/xiaomi_wx08zm/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_wx08zm + name: WX08ZM Activation State + mac_address: 74:a3:4a:b5:07:34 + tablet: + name: WX08ZM Tablet Resource + battery_level: + name: WX08ZM Battery Level diff --git a/tests/components/xiaomi_wx08zm/test.esp32-idf.yaml b/tests/components/xiaomi_wx08zm/test.esp32-idf.yaml new file mode 100644 index 0000000000..3e83ad3e95 --- /dev/null +++ b/tests/components/xiaomi_wx08zm/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_wx08zm + name: WX08ZM Activation State + mac_address: 74:a3:4a:b5:07:34 + tablet: + name: WX08ZM Tablet Resource + battery_level: + name: WX08ZM Battery Level diff --git a/tests/components/xiaomi_wx08zm/test.esp32.yaml b/tests/components/xiaomi_wx08zm/test.esp32.yaml new file mode 100644 index 0000000000..3e83ad3e95 --- /dev/null +++ b/tests/components/xiaomi_wx08zm/test.esp32.yaml @@ -0,0 +1,10 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_wx08zm + name: WX08ZM Activation State + mac_address: 74:a3:4a:b5:07:34 + tablet: + name: WX08ZM Tablet Resource + battery_level: + name: WX08ZM Battery Level diff --git a/tests/components/xl9535/test.esp32-c3-idf.yaml b/tests/components/xl9535/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..178adf870e --- /dev/null +++ b/tests/components/xl9535/test.esp32-c3-idf.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_xl9535 + scl: 5 + sda: 4 + +xl9535: + - id: xl9535_hub + address: 0x20 + +binary_sensor: + - platform: gpio + name: XL9535 Pin 0 + pin: + xl9535: xl9535_hub + number: 0 + mode: + input: true + inverted: false + - platform: gpio + name: XL9535 Pin 17 + pin: + xl9535: xl9535_hub + number: 17 + mode: + input: true + inverted: false diff --git a/tests/components/xl9535/test.esp32-c3.yaml b/tests/components/xl9535/test.esp32-c3.yaml new file mode 100644 index 0000000000..178adf870e --- /dev/null +++ b/tests/components/xl9535/test.esp32-c3.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_xl9535 + scl: 5 + sda: 4 + +xl9535: + - id: xl9535_hub + address: 0x20 + +binary_sensor: + - platform: gpio + name: XL9535 Pin 0 + pin: + xl9535: xl9535_hub + number: 0 + mode: + input: true + inverted: false + - platform: gpio + name: XL9535 Pin 17 + pin: + xl9535: xl9535_hub + number: 17 + mode: + input: true + inverted: false diff --git a/tests/components/xl9535/test.esp32-idf.yaml b/tests/components/xl9535/test.esp32-idf.yaml new file mode 100644 index 0000000000..a65aae890e --- /dev/null +++ b/tests/components/xl9535/test.esp32-idf.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_xl9535 + scl: 16 + sda: 17 + +xl9535: + - id: xl9535_hub + address: 0x20 + +binary_sensor: + - platform: gpio + name: XL9535 Pin 0 + pin: + xl9535: xl9535_hub + number: 0 + mode: + input: true + inverted: false + - platform: gpio + name: XL9535 Pin 17 + pin: + xl9535: xl9535_hub + number: 17 + mode: + input: true + inverted: false diff --git a/tests/components/xl9535/test.esp32.yaml b/tests/components/xl9535/test.esp32.yaml new file mode 100644 index 0000000000..a65aae890e --- /dev/null +++ b/tests/components/xl9535/test.esp32.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_xl9535 + scl: 16 + sda: 17 + +xl9535: + - id: xl9535_hub + address: 0x20 + +binary_sensor: + - platform: gpio + name: XL9535 Pin 0 + pin: + xl9535: xl9535_hub + number: 0 + mode: + input: true + inverted: false + - platform: gpio + name: XL9535 Pin 17 + pin: + xl9535: xl9535_hub + number: 17 + mode: + input: true + inverted: false diff --git a/tests/components/xl9535/test.esp8266.yaml b/tests/components/xl9535/test.esp8266.yaml new file mode 100644 index 0000000000..178adf870e --- /dev/null +++ b/tests/components/xl9535/test.esp8266.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_xl9535 + scl: 5 + sda: 4 + +xl9535: + - id: xl9535_hub + address: 0x20 + +binary_sensor: + - platform: gpio + name: XL9535 Pin 0 + pin: + xl9535: xl9535_hub + number: 0 + mode: + input: true + inverted: false + - platform: gpio + name: XL9535 Pin 17 + pin: + xl9535: xl9535_hub + number: 17 + mode: + input: true + inverted: false diff --git a/tests/components/xl9535/test.rp2040.yaml b/tests/components/xl9535/test.rp2040.yaml new file mode 100644 index 0000000000..178adf870e --- /dev/null +++ b/tests/components/xl9535/test.rp2040.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_xl9535 + scl: 5 + sda: 4 + +xl9535: + - id: xl9535_hub + address: 0x20 + +binary_sensor: + - platform: gpio + name: XL9535 Pin 0 + pin: + xl9535: xl9535_hub + number: 0 + mode: + input: true + inverted: false + - platform: gpio + name: XL9535 Pin 17 + pin: + xl9535: xl9535_hub + number: 17 + mode: + input: true + inverted: false diff --git a/tests/components/xpt2046/test.esp32-c3-idf.yaml b/tests/components/xpt2046/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..f3a2cf9aae --- /dev/null +++ b/tests/components/xpt2046/test.esp32-c3-idf.yaml @@ -0,0 +1,34 @@ +spi: + - id: spi_xpt2046 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: xpt_display + dimensions: 320x240 + model: TFT 2.4 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 4 + interrupt_pin: 3 + display: xpt_display + update_interval: 50ms + threshold: 400 + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/xpt2046/test.esp32-c3.yaml b/tests/components/xpt2046/test.esp32-c3.yaml new file mode 100644 index 0000000000..f3a2cf9aae --- /dev/null +++ b/tests/components/xpt2046/test.esp32-c3.yaml @@ -0,0 +1,34 @@ +spi: + - id: spi_xpt2046 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: xpt_display + dimensions: 320x240 + model: TFT 2.4 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 4 + interrupt_pin: 3 + display: xpt_display + update_interval: 50ms + threshold: 400 + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/xpt2046/test.esp32-idf.yaml b/tests/components/xpt2046/test.esp32-idf.yaml new file mode 100644 index 0000000000..bb166866f4 --- /dev/null +++ b/tests/components/xpt2046/test.esp32-idf.yaml @@ -0,0 +1,34 @@ +spi: + - id: spi_xpt2046 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: xpt_display + dimensions: 320x240 + model: TFT 2.4 + cs_pin: 13 + dc_pin: 14 + reset_pin: 21 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 18 + interrupt_pin: 19 + display: xpt_display + update_interval: 50ms + threshold: 400 + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/xpt2046/test.esp32-s2.yaml b/tests/components/xpt2046/test.esp32-s2.yaml new file mode 100644 index 0000000000..6232ca957b --- /dev/null +++ b/tests/components/xpt2046/test.esp32-s2.yaml @@ -0,0 +1,37 @@ +spi: + clk_pin: 7 + mosi_pin: 11 + miso_pin: 9 + +display: + - platform: ili9xxx + id: my_display + model: ili9341 + cs_pin: 5 + dc_pin: 12 + reset_pin: 33 + auto_clear_enabled: false + data_rate: 40MHz + dimensions: 320x240 + update_interval: never + transform: + mirror_y: false + mirror_x: false + swap_xy: true + +touchscreen: + - platform: xpt2046 + display: my_display + id: my_toucher + update_interval: 50ms + cs_pin: 18 + threshold: 300 + calibration: + x_min: 210 + x_max: 3890 + y_min: 170 + y_max: 3730 + transform: + mirror_x: false + mirror_y: true + swap_xy: true diff --git a/tests/components/xpt2046/test.esp32.yaml b/tests/components/xpt2046/test.esp32.yaml new file mode 100644 index 0000000000..bb166866f4 --- /dev/null +++ b/tests/components/xpt2046/test.esp32.yaml @@ -0,0 +1,34 @@ +spi: + - id: spi_xpt2046 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: xpt_display + dimensions: 320x240 + model: TFT 2.4 + cs_pin: 13 + dc_pin: 14 + reset_pin: 21 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 18 + interrupt_pin: 19 + display: xpt_display + update_interval: 50ms + threshold: 400 + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/xpt2046/test.esp8266.yaml b/tests/components/xpt2046/test.esp8266.yaml new file mode 100644 index 0000000000..a917290e8e --- /dev/null +++ b/tests/components/xpt2046/test.esp8266.yaml @@ -0,0 +1,34 @@ +spi: + - id: spi_xpt2046 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ili9xxx + id: xpt_display + dimensions: 320x240 + model: TFT 2.4 + cs_pin: 15 + dc_pin: 4 + reset_pin: 5 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 0 + interrupt_pin: 16 + display: xpt_display + update_interval: 50ms + threshold: 400 + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/xpt2046/test.rp2040.yaml b/tests/components/xpt2046/test.rp2040.yaml new file mode 100644 index 0000000000..a7a49309ac --- /dev/null +++ b/tests/components/xpt2046/test.rp2040.yaml @@ -0,0 +1,34 @@ +spi: + - id: spi_xpt2046 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ili9xxx + id: xpt_display + dimensions: 320x240 + model: TFT 2.4 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 5 + interrupt_pin: 6 + display: xpt_display + update_interval: 50ms + threshold: 400 + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/yashima/test.esp32-c3-idf.yaml b/tests/components/yashima/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4b6d6daee4 --- /dev/null +++ b/tests/components/yashima/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: yashima + name: Yashima Climate diff --git a/tests/components/yashima/test.esp32-c3.yaml b/tests/components/yashima/test.esp32-c3.yaml new file mode 100644 index 0000000000..4b6d6daee4 --- /dev/null +++ b/tests/components/yashima/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: yashima + name: Yashima Climate diff --git a/tests/components/yashima/test.esp32-idf.yaml b/tests/components/yashima/test.esp32-idf.yaml new file mode 100644 index 0000000000..4b6d6daee4 --- /dev/null +++ b/tests/components/yashima/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: yashima + name: Yashima Climate diff --git a/tests/components/yashima/test.esp32.yaml b/tests/components/yashima/test.esp32.yaml new file mode 100644 index 0000000000..4b6d6daee4 --- /dev/null +++ b/tests/components/yashima/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: yashima + name: Yashima Climate diff --git a/tests/components/yashima/test.esp8266.yaml b/tests/components/yashima/test.esp8266.yaml new file mode 100644 index 0000000000..296a7ede25 --- /dev/null +++ b/tests/components/yashima/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: yashima + name: Yashima Climate diff --git a/tests/components/zhlt01/test.esp32-c3-idf.yaml b/tests/components/zhlt01/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..d1dc3b4926 --- /dev/null +++ b/tests/components/zhlt01/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: zhlt01 + name: ZH/LT-01 Climate diff --git a/tests/components/zhlt01/test.esp32-c3.yaml b/tests/components/zhlt01/test.esp32-c3.yaml new file mode 100644 index 0000000000..d1dc3b4926 --- /dev/null +++ b/tests/components/zhlt01/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: zhlt01 + name: ZH/LT-01 Climate diff --git a/tests/components/zhlt01/test.esp32-idf.yaml b/tests/components/zhlt01/test.esp32-idf.yaml new file mode 100644 index 0000000000..d1dc3b4926 --- /dev/null +++ b/tests/components/zhlt01/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: zhlt01 + name: ZH/LT-01 Climate diff --git a/tests/components/zhlt01/test.esp32.yaml b/tests/components/zhlt01/test.esp32.yaml new file mode 100644 index 0000000000..d1dc3b4926 --- /dev/null +++ b/tests/components/zhlt01/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: zhlt01 + name: ZH/LT-01 Climate diff --git a/tests/components/zhlt01/test.esp8266.yaml b/tests/components/zhlt01/test.esp8266.yaml new file mode 100644 index 0000000000..40a00bc458 --- /dev/null +++ b/tests/components/zhlt01/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: zhlt01 + name: ZH/LT-01 Climate diff --git a/tests/components/zio_ultrasonic/test.esp32-c3-idf.yaml b/tests/components/zio_ultrasonic/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..36e1697a38 --- /dev/null +++ b/tests/components/zio_ultrasonic/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_zio_ultrasonic + scl: 5 + sda: 4 + +sensor: + - platform: zio_ultrasonic + name: "Distance" + update_interval: 60s diff --git a/tests/components/zio_ultrasonic/test.esp32-c3.yaml b/tests/components/zio_ultrasonic/test.esp32-c3.yaml new file mode 100644 index 0000000000..36e1697a38 --- /dev/null +++ b/tests/components/zio_ultrasonic/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_zio_ultrasonic + scl: 5 + sda: 4 + +sensor: + - platform: zio_ultrasonic + name: "Distance" + update_interval: 60s diff --git a/tests/components/zio_ultrasonic/test.esp32-idf.yaml b/tests/components/zio_ultrasonic/test.esp32-idf.yaml new file mode 100644 index 0000000000..ad4050307e --- /dev/null +++ b/tests/components/zio_ultrasonic/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_zio_ultrasonic + scl: 16 + sda: 17 + +sensor: + - platform: zio_ultrasonic + name: "Distance" + update_interval: 60s diff --git a/tests/components/zio_ultrasonic/test.esp32.yaml b/tests/components/zio_ultrasonic/test.esp32.yaml new file mode 100644 index 0000000000..ad4050307e --- /dev/null +++ b/tests/components/zio_ultrasonic/test.esp32.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_zio_ultrasonic + scl: 16 + sda: 17 + +sensor: + - platform: zio_ultrasonic + name: "Distance" + update_interval: 60s diff --git a/tests/components/zio_ultrasonic/test.esp8266.yaml b/tests/components/zio_ultrasonic/test.esp8266.yaml new file mode 100644 index 0000000000..36e1697a38 --- /dev/null +++ b/tests/components/zio_ultrasonic/test.esp8266.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_zio_ultrasonic + scl: 5 + sda: 4 + +sensor: + - platform: zio_ultrasonic + name: "Distance" + update_interval: 60s diff --git a/tests/components/zio_ultrasonic/test.rp2040.yaml b/tests/components/zio_ultrasonic/test.rp2040.yaml new file mode 100644 index 0000000000..36e1697a38 --- /dev/null +++ b/tests/components/zio_ultrasonic/test.rp2040.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_zio_ultrasonic + scl: 5 + sda: 4 + +sensor: + - platform: zio_ultrasonic + name: "Distance" + update_interval: 60s diff --git a/tests/components/zyaura/test.esp32-c3-idf.yaml b/tests/components/zyaura/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..90205c468c --- /dev/null +++ b/tests/components/zyaura/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: zyaura + clock_pin: 5 + data_pin: 4 + co2: + name: ZyAura CO2 + temperature: + name: ZyAura Temperature + humidity: + name: ZyAura Humidity diff --git a/tests/components/zyaura/test.esp32-c3.yaml b/tests/components/zyaura/test.esp32-c3.yaml new file mode 100644 index 0000000000..90205c468c --- /dev/null +++ b/tests/components/zyaura/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: zyaura + clock_pin: 5 + data_pin: 4 + co2: + name: ZyAura CO2 + temperature: + name: ZyAura Temperature + humidity: + name: ZyAura Humidity diff --git a/tests/components/zyaura/test.esp32-idf.yaml b/tests/components/zyaura/test.esp32-idf.yaml new file mode 100644 index 0000000000..29116a978b --- /dev/null +++ b/tests/components/zyaura/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: zyaura + clock_pin: 16 + data_pin: 17 + co2: + name: ZyAura CO2 + temperature: + name: ZyAura Temperature + humidity: + name: ZyAura Humidity diff --git a/tests/components/zyaura/test.esp32.yaml b/tests/components/zyaura/test.esp32.yaml new file mode 100644 index 0000000000..29116a978b --- /dev/null +++ b/tests/components/zyaura/test.esp32.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: zyaura + clock_pin: 16 + data_pin: 17 + co2: + name: ZyAura CO2 + temperature: + name: ZyAura Temperature + humidity: + name: ZyAura Humidity diff --git a/tests/components/zyaura/test.esp8266.yaml b/tests/components/zyaura/test.esp8266.yaml new file mode 100644 index 0000000000..90205c468c --- /dev/null +++ b/tests/components/zyaura/test.esp8266.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: zyaura + clock_pin: 5 + data_pin: 4 + co2: + name: ZyAura CO2 + temperature: + name: ZyAura Temperature + humidity: + name: ZyAura Humidity diff --git a/tests/components/zyaura/test.rp2040.yaml b/tests/components/zyaura/test.rp2040.yaml new file mode 100644 index 0000000000..90205c468c --- /dev/null +++ b/tests/components/zyaura/test.rp2040.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: zyaura + clock_pin: 5 + data_pin: 4 + co2: + name: ZyAura CO2 + temperature: + name: ZyAura Temperature + humidity: + name: ZyAura Humidity diff --git a/tests/dashboard/util/test_file.py b/tests/dashboard/util/test_file.py index 89e6b97086..51ba10b328 100644 --- a/tests/dashboard/util/test_file.py +++ b/tests/dashboard/util/test_file.py @@ -13,7 +13,7 @@ def test_write_utf8_file(tmp_path: Path) -> None: assert tmp_path.joinpath("foo.txt").read_text() == "foo" with pytest.raises(OSError): - write_utf8_file(Path("/not-writable"), "bar") + write_utf8_file(Path("/dev/not-writable"), "bar") def test_write_file(tmp_path: Path) -> None: @@ -28,8 +28,9 @@ def test_write_utf8_file_fails_at_rename( test_dir = tmpdir.mkdir("files") test_file = Path(test_dir / "test.json") - with pytest.raises(OSError), patch( - "esphome.dashboard.util.file.os.replace", side_effect=OSError + with ( + pytest.raises(OSError), + patch("esphome.dashboard.util.file.os.replace", side_effect=OSError), ): write_utf8_file(test_file, '{"some":"data"}', False) @@ -45,9 +46,11 @@ def test_write_utf8_file_fails_at_rename_and_remove( test_dir = tmpdir.mkdir("files") test_file = Path(test_dir / "test.json") - with pytest.raises(OSError), patch( - "esphome.dashboard.util.file.os.remove", side_effect=OSError - ), patch("esphome.dashboard.util.file.os.replace", side_effect=OSError): + with ( + pytest.raises(OSError), + patch("esphome.dashboard.util.file.os.remove", side_effect=OSError), + patch("esphome.dashboard.util.file.os.replace", side_effect=OSError), + ): write_utf8_file(test_file, '{"some":"data"}', False) assert "File replacement cleanup failed" in caplog.text diff --git a/tests/test1.yaml b/tests/test1.yaml index f7b433cce2..c8ae9691c2 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -322,6 +322,26 @@ ads1115: address: 0x48 i2c_id: i2c_bus +ads1118: + spi_id: spi_bus + cs_pin: + allow_other_uses: true + number: GPIO12 + +as5600: + i2c_id: i2c_bus + dir_pin: + number: 27 + allow_other_uses: true + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 + dallas: pin: allow_other_uses: true @@ -344,8 +364,10 @@ esp32_ble_tracker: ble_client: - mac_address: AA:BB:CC:DD:EE:FF id: ble_foo + auto_connect: true - mac_address: 11:22:33:44:55:66 id: ble_blah + auto_connect: false on_connect: then: - switch.turn_on: ble1_status @@ -364,7 +386,7 @@ ble_client: then: - ble_client.numeric_comparison_reply: id: ble_blah - accept: True + accept: true - mac_address: C4:4F:33:11:22:33 id: my_bedjet_ble_client @@ -555,6 +577,24 @@ sensor: state_topic: hi/me retain: false availability: + - platform: ads1118 + name: ads1118 adc + multiplexer: A0_A1 + gain: 1.024 + type: adc + - platform: ads1118 + name: ads1118 temperature + type: temperature + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status - platform: as7341 update_interval: 15s gain: X8 @@ -653,6 +693,7 @@ sensor: internal: true address: 0x23 update_interval: 30s + qos: 2 retain: false availability: state_topic: livingroom/custom_state_topic @@ -664,7 +705,7 @@ sensor: update_interval: 30s mode: low_power i2c_id: i2c_bus - - platform: bme280 + - platform: bme280_i2c temperature: name: Outside Temperature oversampling: 16x @@ -678,6 +719,21 @@ sensor: iir_filter: 16x update_interval: 15s i2c_id: i2c_bus + - platform: bme280_spi + temperature: + name: Outside Temperature + oversampling: 16x + pressure: + name: Outside Pressure + oversampling: none + humidity: + name: Outside Humidity + oversampling: 8x + cs_pin: + allow_other_uses: true + number: GPIO23 + iir_filter: 16x + update_interval: 15s - platform: bme680 temperature: name: Outside Temperature @@ -794,7 +850,7 @@ sensor: update_interval: 15s current_resistor: 0.001 ohm voltage_divider: 2351 - change_mode_every: 16 + change_mode_every: "never" initial_mode: VOLTAGE model: hlw8012 - platform: total_daily_energy @@ -822,6 +878,13 @@ sensor: oversampling: 8x update_interval: 15s i2c_id: i2c_bus + - platform: honeywell_hih_i2c + temperature: + name: Living Room Temperature 7 + humidity: + name: Living Room Humidity 7 + update_interval: 15s + i2c_id: i2c_bus - platform: honeywellabp pressure: name: Honeywell pressure @@ -923,7 +986,8 @@ sensor: name: Internal Ttemperature update_interval: 15s i2c_id: i2c_bus - - platform: kalman_combinator + - platform: combination + type: kalman name: Kalman-filtered temperature process_std_dev: 0.00139 sources: @@ -932,6 +996,57 @@ sensor: return 0.4 + std::abs(x - 25) * 0.023; - source: scd4x_temperature error: 1.5 + - platform: combination + type: linear + name: Linearly combined temperatures + sources: + - source: scd30_temperature + coeffecient: !lambda |- + return 0.4 + std::abs(x - 25) * 0.023; + - source: scd4x_temperature + coeffecient: 1.5 + - platform: combination + type: max + name: Max of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature + - platform: combination + type: mean + name: Mean of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature + - platform: combination + type: median + name: Median of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature + - platform: combination + type: min + name: Min of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature + - platform: combination + type: most_recently_updated + name: Most recently updated of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature + - platform: combination + type: range + name: Range of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature + - platform: combination + type: sum + name: Sum of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature - platform: htu21d temperature: name: Living Room Temperature 6 @@ -1329,6 +1444,16 @@ sensor: name: tsl2591 calculated_lux id: tsl2591_cl i2c_id: i2c_bus + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + i2c_id: i2c_bus + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms - platform: tee501 name: Office Temperature 3 address: 0x48 @@ -1648,6 +1773,62 @@ sensor: memory_location: 0x20 memory_address: 0x7d name: Adres sensor + - platform: ade7880 + i2c_id: i2c_bus + irq0_pin: + number: GPIO13 + allow_other_uses: true + irq1_pin: + number: GPIO5 + allow_other_uses: true + reset_pin: + number: GPIO16 + allow_other_uses: true + frequency: 60Hz + phase_a: + name: Channel A + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3116628 + voltage_gain: -757178 + power_gain: -1344457 + phase_angle: 188 + phase_b: + name: Channel B + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3133655 + voltage_gain: -755235 + power_gain: -1345638 + phase_angle: 188 + phase_c: + name: Channel C + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3111158 + voltage_gain: -743813 + power_gain: -1351437 + phase_angle: 180 + neutral: + name: Neutral + current: Current + calibration: + current_gain: 3189 psram: @@ -1667,7 +1848,6 @@ binary_sensor: mcp23xxx: mcp23s08_hub # Use pin number 1 number: 1 - allow_other_uses: true # One of INPUT or INPUT_PULLUP mode: INPUT_PULLUP inverted: false @@ -1709,6 +1889,8 @@ binary_sensor: - delayed_on_off: !lambda "return 10;" - delayed_on: !lambda "return 1000;" - delayed_off: !lambda "return 0;" + - settle: 40ms + - settle: !lambda "return 10;" on_press: then: - lambda: >- @@ -1886,7 +2068,7 @@ binary_sensor: on_press: - fan.cycle_speed: id: fan_speed - off_speed_cycle: False + off_speed_cycle: false - logger.log: "Cycle speed clicked" - platform: remote_receiver name: Raw Remote Receiver Test @@ -1986,6 +2168,18 @@ binary_sensor: - platform: dfrobot_sen0395 id: mmwave_detected_uart dfrobot_sen0395_id: mmwave + - platform: nfc + nfcc_id: nfcc_pn7160_i2c + ndef_contains: pulse + name: MFC Tag 1 + - platform: nfc + nfcc_id: nfcc_pn7160_i2c + tag_id: pulse + name: MFC Tag 2 + - platform: nfc + nfcc_id: nfcc_pn7160_i2c + uid: 59-FC-AB-15 + name: MFC Tag 3 pca9685: frequency: 500 @@ -2016,21 +2210,21 @@ my9231: sm2235: data_pin: - allow_other_uses: true - number: GPIO4 + allow_other_uses: true + number: GPIO4 clock_pin: - allow_other_uses: true - number: GPIO5 + allow_other_uses: true + number: GPIO5 max_power_color_channels: 9 max_power_white_channels: 9 sm2335: data_pin: - allow_other_uses: true - number: GPIO4 + allow_other_uses: true + number: GPIO4 clock_pin: - allow_other_uses: true - number: GPIO5 + allow_other_uses: true + number: GPIO5 max_power_color_channels: 9 max_power_white_channels: 9 @@ -2133,7 +2327,7 @@ output: pin: pcf8574: pcf8574_hub number: 0 - #allow_other_uses: true + # allow_other_uses: true mode: OUTPUT inverted: false - platform: gpio @@ -2141,7 +2335,7 @@ output: pin: pca9554: pca9554_hub number: 0 - #allow_other_uses: true + # allow_other_uses: true mode: OUTPUT inverted: false - platform: gpio @@ -2149,7 +2343,6 @@ output: pin: mcp23xxx: mcp23017_hub number: 0 - allow_other_uses: true mode: OUTPUT inverted: false - platform: gpio @@ -2157,7 +2350,6 @@ output: pin: mcp23xxx: mcp23008_hub number: 0 - allow_other_uses: true mode: OUTPUT inverted: false - platform: gpio @@ -2460,6 +2652,10 @@ climate: name: Yashima Climate - platform: mitsubishi name: Mitsubishi + supports_dry: "true" + supports_fan_only: "true" + horizontal_default: "left" + vertical_default: "down" - platform: whirlpool name: Whirlpool Climate - platform: climate_ir_lg @@ -2597,7 +2793,6 @@ switch: mcp23xxx: mcp23s08_hub # Use pin number 0 number: 0 - allow_other_uses: true mode: OUTPUT inverted: false - platform: gpio @@ -3004,6 +3199,16 @@ interval: page_id: page1 then: - logger.log: Seeing page 1 + - interval: 60min + then: + - ble_client.connect: ble_blah + - ble_client.ble_write: + id: ble_blah + service_uuid: EBE0CCB0-7A0A-4B0C-8A1A-6FF2997DA3A6 + characteristic_uuid: EBE0CCB7-7A0A-4B0C-8A1A-6FF2997DA3A6 + value: !lambda |- + return {1, 0}; + - ble_client.disconnect: ble_blah color: - id: kbx_red @@ -3022,17 +3227,13 @@ display: id: my_lcd_gpio dimensions: 18x4 data_pins: - - - allow_other_uses: true + - allow_other_uses: true number: GPIO19 - - - allow_other_uses: true + - allow_other_uses: true number: GPIO21 - - - allow_other_uses: true + - allow_other_uses: true number: GPIO22 - - - allow_other_uses: true + - allow_other_uses: true number: GPIO23 enable_pin: allow_other_uses: true @@ -3229,7 +3430,7 @@ display: reset_pin: allow_other_uses: true number: GPIO23 - backlight_pin: no + backlight_pin: false lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7920 @@ -3241,6 +3442,25 @@ display: inverted: true lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: st7567_i2c + id: st7735_display_i2c + address: 0x3F + i2c_id: i2c_bus + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: st7567_spi + id: st7735_display_spi + cs_pin: + allow_other_uses: true + number: GPIO5 + dc_pin: + allow_other_uses: true + number: GPIO16 + reset_pin: + allow_other_uses: true + number: GPIO23 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7735 id: st7735_display model: INITR_BLACKTAB @@ -3389,6 +3609,7 @@ pn532_i2c: i2c_id: i2c_bus pn7150_i2c: + id: nfcc_pn7150_i2c i2c_id: i2c_bus irq_pin: allow_other_uses: true @@ -3721,6 +3942,10 @@ text_sensor: - platform: template name: Template Text Sensor id: ${textname}_text + - platform: template + name: Template Text Sensor Timestamp + id: ${textname}_text_timestamp + device_class: timestamp - platform: wifi_info scan_results: name: Scan Results @@ -4183,25 +4408,25 @@ graphical_display_menu: lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' items: - type: back - text: 'Back' + text: "Back" - type: label - type: menu - text: 'Submenu 1' + text: "Submenu 1" items: - type: back - text: 'Back' + text: "Back" - type: menu - text: 'Submenu 21' + text: "Submenu 21" items: - type: back - text: 'Back' + text: "Back" - type: command - text: 'Show Main' + text: "Show Main" on_value: then: - display_menu.show_main: test_graphical_display_menu - type: select - text: 'Enum Item' + text: "Enum Item" immediate_edit: true select: test_select on_enter: @@ -4214,7 +4439,7 @@ graphical_display_menu: then: lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' - type: number - text: 'Number' + text: "Number" number: test_number on_enter: then: @@ -4226,15 +4451,15 @@ graphical_display_menu: then: lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' - type: command - text: 'Hide' + text: "Hide" on_value: then: - display_menu.hide: test_graphical_display_menu - type: switch - text: 'Switch' + text: "Switch" switch: my_switch - on_text: 'Bright' - off_text: 'Dark' + on_text: "Bright" + off_text: "Dark" immediate_edit: false on_value: then: diff --git a/tests/test11.5.yaml b/tests/test11.5.yaml index 2a9b40c5c3..ef260d79c0 100644 --- a/tests/test11.5.yaml +++ b/tests/test11.5.yaml @@ -116,7 +116,7 @@ binary_sensor: id: modbus_binsensortest register_type: read address: 0x3200 - bitmask: 0x80 # (bit 8) + bitmask: 0x80 # (bit 8) lambda: "return x;" - platform: tm1638 @@ -714,9 +714,9 @@ display: id: primarydisplay stb_pin: allow_other_uses: true - number: 5 #TM1638 STB - clk_pin: 18 #TM1638 CLK - dio_pin: 23 #TM1638 DIO + number: 5 # TM1638 STB + clk_pin: 18 # TM1638 CLK + dio_pin: 23 # TM1638 DIO update_interval: 5s intensity: 5 lambda: |- diff --git a/tests/test2.yaml b/tests/test2.yaml index e5358781df..a1e310be9c 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -8,7 +8,7 @@ esphome: globals: - id: my_global_string type: std::string - restore_value: yes + restore_value: true max_restore_data_length: 70 initial_value: '"DefaultValue"' @@ -501,6 +501,7 @@ binary_sensor: - platform: ble_presence mac_address: AC:37:43:77:5F:4C name: ESP32 BLE Tracker Google Home Mini + timeout: 30s - platform: ble_presence service_uuid: 11aa name: BLE Test Service 16 Presence @@ -786,11 +787,11 @@ image: - id: rgb24_image file: pnglogo.png type: RGB24 - use_transparency: yes + use_transparency: true - id: rgb565_image file: pnglogo.png type: RGB565 - use_transparency: no + use_transparency: false - id: web_svg_image file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg resize: 256x48 @@ -811,11 +812,6 @@ image: file: mdi:alert-outline type: BINARY -font: - - file: "gfonts://Roboto" - id: roboto - size: 20 - graph: - id: my_graph sensor: ha_hello_world_temperature diff --git a/tests/test3.1.yaml b/tests/test3.1.yaml index 63ef4e8ce0..2bddd6f4d7 100644 --- a/tests/test3.1.yaml +++ b/tests/test3.1.yaml @@ -49,6 +49,7 @@ spi: number: GPIO14 ota: + version: 2 logger: @@ -289,6 +290,62 @@ sensor: id: adc128s102_channel_0 channel: 0 + - platform: ade7880 + irq0_pin: + number: GPIO13 + allow_other_uses: true + irq1_pin: + number: GPIO5 + allow_other_uses: true + reset_pin: + number: GPIO16 + allow_other_uses: true + frequency: 60Hz + phase_a: + name: Channel A + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3116628 + voltage_gain: -757178 + power_gain: -1344457 + phase_angle: 188 + phase_b: + name: Channel B + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3133655 + voltage_gain: -755235 + power_gain: -1345638 + phase_angle: 188 + phase_c: + name: Channel C + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3111158 + voltage_gain: -743813 + power_gain: -1351437 + phase_angle: 180 + neutral: + name: Neutral + current: Current + calibration: + current_gain: 3189 + apds9960: address: 0x20 update_interval: 60s @@ -401,7 +458,6 @@ switch: pin: mcp23xxx: mcp23017_hub number: 0 - allow_other_uses: true mode: OUTPUT interlock: &interlock [gpio_switch1, gpio_switch2, gpio_switch3] - platform: gpio @@ -409,7 +465,6 @@ switch: pin: mcp23xxx: mcp23008_hub number: 0 - allow_other_uses: true mode: OUTPUT interlock: *interlock - platform: gpio @@ -448,7 +503,7 @@ switch: - platform: template name: open_vent id: open_vent - optimistic: True + optimistic: true on_turn_on: then: - grove_tb6612fng.run: diff --git a/tests/test3.yaml b/tests/test3.yaml index ab7f38d07f..61d814385b 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -409,6 +409,7 @@ sensor: name: hydreon_total_acc r_int: name: hydreon_r_int + resolution: low - platform: adc pin: VCC @@ -634,7 +635,11 @@ sensor: current: name: CSE7766 Current power: - name: CSE776 Power + name: CSE7766 Power + apparent_power: + name: CSE7766 Apparent Power + power_factor: + name: CSE7766 Power Factor - platform: fingerprint_grow fingerprint_count: @@ -743,6 +748,31 @@ sensor: temperature: name: Kuntze temperature + - platform: haier + haier_id: haier_climate + compressor_current: + name: Haier AC compressor current + compressor_frequency: + name: Haier AC compressor frequency + expansion_valve_open_degree: + name: Haier AC expansion valve open degree + humidity: + name: Haier AC indoor humidity + indoor_coil_temperature: + name: Haier AC indoor coil temperature + outdoor_coil_temperature: + name: Haier AC outdoor coil temperature + outdoor_defrost_temperature: + name: Haier AC outdoor defrost temperature + outdoor_in_air_temperature: + name: Haier AC outdoor in air temperature + outdoor_out_air_temperature: + name: Haier AC outdoor out air temperature + outdoor_temperature: + name: Haier AC outdoor temperature + power: + name: Haier AC power + time: - platform: homeassistant @@ -809,6 +839,21 @@ binary_sensor: allow_other_uses: true number: 3 + - platform: haier + haier_id: haier_climate + compressor_status: + name: Haier AC compressor status + defrost_status: + name: Haier AC defrost status + four_way_valve_status: + name: Haier AC four-way valve status + indoor_electric_heating_status: + name: Haier AC indoor electric heating status + indoor_fan_status: + name: Haier AC indoor fan status + outdoor_fan_status: + name: Haier AC outdoor fan status + globals: - id: my_global_string type: std::string @@ -1020,29 +1065,48 @@ climate: kd_multiplier: 0.0 deadband_output_averaging_samples: 1 - platform: haier + id: haier_climate protocol: hOn name: Haier AC uart_id: uart_12 wifi_signal: true + answer_timeout: 200ms beeper: true - outdoor_temperature: - name: Haier AC outdoor temperature visual: min_temperature: 16 °C max_temperature: 30 °C - temperature_step: 1 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 supported_modes: - - 'OFF' - - HEAT_COOL - - COOL - - HEAT - - DRY - - FAN_ONLY + - "OFF" + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY supported_swing_modes: - - 'OFF' - - VERTICAL - - HORIZONTAL - - BOTH + - "OFF" + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: 'Alarm activated. Code: %d. Message: "%s"' + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: 'Alarm deactivated. Code: %d. Message: "%s"' + args: [code, message] sprinkler: - id: yard_sprinkler_ctrlr @@ -1236,8 +1300,19 @@ fingerprint_grow: sensing_pin: allow_other_uses: true number: 4 + sensor_power_pin: + allow_other_uses: true + number: 5 + inverted: true + idle_period_to_sleep: 5s password: 0x12FE37DC new_password: 0xA65B9840 + on_finger_scan_start: + - homeassistant.event: + event: esphome.${device_name}_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - homeassistant.event: + event: esphome.${device_name}_fingerprint_grow_finger_scan_invalid on_finger_scan_matched: - homeassistant.event: event: esphome.${device_name}_fingerprint_grow_finger_scan_matched @@ -1247,6 +1322,9 @@ fingerprint_grow: on_finger_scan_unmatched: - homeassistant.event: event: esphome.${device_name}_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - homeassistant.event: + event: esphome.${device_name}_fingerprint_grow_finger_scan_misplaced on_enrollment_scan: - homeassistant.event: event: esphome.${device_name}_fingerprint_grow_enrollment_scan diff --git a/tests/test4.yaml b/tests/test4.yaml index 089caf073b..993ce126a8 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -477,7 +477,6 @@ binary_sensor: sx1509: sx1509_hub number: 3 - - platform: touchscreen touchscreen_id: lilygo_touchscreen id: touch_key1 @@ -491,7 +490,6 @@ binary_sensor: id: touch_key_911 index: 0 - - platform: gpio name: MaxIn Pin 4 pin: @@ -521,7 +519,6 @@ binary_sensor: input: true inverted: false - climate: - platform: tuya id: tuya_climate @@ -591,7 +588,6 @@ cover: open_duration: 14s close_duration: 14s - display: - platform: addressable_light id: led_matrix_32x8_display @@ -616,84 +612,11 @@ display: rotation: 0° update_interval: 16ms - - platform: waveshare_epaper - spi_id: spi_id_1 - cs_pin: - allow_other_uses: true - number: GPIO23 - dc_pin: - allow_other_uses: true - number: GPIO23 - busy_pin: - allow_other_uses: true - number: GPIO23 - reset_pin: - allow_other_uses: true - number: GPIO23 - model: 2.13in-ttgo-b1 - full_update_every: 30 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - - platform: waveshare_epaper - spi_id: spi_id_1 - cs_pin: - allow_other_uses: true - number: GPIO23 - dc_pin: - allow_other_uses: true - number: GPIO23 - busy_pin: - allow_other_uses: true - number: GPIO23 - reset_pin: - allow_other_uses: true - number: GPIO23 - model: 2.90in - full_update_every: 30 - reset_duration: 200ms - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - - platform: waveshare_epaper - spi_id: spi_id_1 - cs_pin: - allow_other_uses: true - number: GPIO23 - dc_pin: - allow_other_uses: true - number: GPIO23 - busy_pin: - allow_other_uses: true - number: GPIO23 - reset_pin: - allow_other_uses: true - number: GPIO23 - model: 2.90inv2 - full_update_every: 30 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - - platform: waveshare_epaper - spi_id: spi_id_1 - cs_pin: - allow_other_uses: true - number: GPIO23 - dc_pin: - allow_other_uses: true - number: GPIO23 - busy_pin: - allow_other_uses: true - number: GPIO23 - reset_pin: - allow_other_uses: true - number: GPIO23 - model: 1.54in-m5coreink-m09 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: inkplate6 id: inkplate_display greyscale: false partial_updating: false update_interval: 60s - display_data_1_pin: number: GPIO5 allow_other_uses: true @@ -834,16 +757,16 @@ esp32_camera: allow_other_uses: true - number: GPIO35 allow_other_uses: true - - number: GPIO34 - - number: GPIO5 + - number: GPIO34 + - number: GPIO5 allow_other_uses: true - - number: GPIO39 + - number: GPIO39 allow_other_uses: true - - number: GPIO18 + - number: GPIO18 allow_other_uses: true - - number: GPIO36 + - number: GPIO36 allow_other_uses: true - - number: GPIO19 + - number: GPIO19 allow_other_uses: true vsync_pin: allow_other_uses: true @@ -876,8 +799,8 @@ esp32_camera: jpeg_quality: 10 on_image: then: - - lambda: |- - ESP_LOGD("main", "image len=%d, data=%c", image.length, image.data[0]); + - lambda: |- + ESP_LOGD("main", "image len=%d, data=%c", image.length, image.data[0]); esp32_camera_web_server: - port: 8080 @@ -929,10 +852,11 @@ touchscreen: display: inkplate_display update_interval: 50ms threshold: 400 - calibration_x_min: 3860 - calibration_x_max: 280 - calibration_y_min: 340 - calibration_y_max: 3860 + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 on_touch: - logger.log: format: Touch at (%d, %d) @@ -953,7 +877,6 @@ touchscreen: number: GPIO3 display: inkplate_display - - platform: ft63x6 id: ft63_touchscreen interrupt_pin: @@ -961,7 +884,7 @@ touchscreen: number: GPIO39 reset_pin: allow_other_uses: true - number: GPIO5 + number: GPIO5 display: inkplate_display on_touch: - logger.log: diff --git a/tests/test5.yaml b/tests/test5.yaml index bf4247fb92..81615b24b0 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -100,7 +100,7 @@ binary_sensor: id: modbus_binsensortest register_type: read address: 0x3200 - bitmask: 0x80 # (bit 8) + bitmask: 0x80 # (bit 8) lambda: "return x;" - platform: tm1638 @@ -483,6 +483,29 @@ sensor: address: 0x77 iir_filter: 2X + - platform: ms8607 + temperature: + name: Temperature + humidity: + name: Humidity + pressure: + name: Pressure + - platform: ms8607 + id: ms8607_more_config + temperature: + name: Indoor Temperature + accuracy_decimals: 1 + pressure: + name: Indoor Pressure + internal: true + humidity: + name: Indoor Humidity + address: 0x41 + i2c_id: + i2c_id: + address: 0x77 + update_interval: 10min + - platform: sen5x id: sen54 temperature: @@ -632,9 +655,9 @@ display: id: primarydisplay stb_pin: allow_other_uses: true - number: 5 #TM1638 STB - clk_pin: 18 #TM1638 CLK - dio_pin: 23 #TM1638 DIO + number: 5 # TM1638 STB + clk_pin: 18 # TM1638 CLK + dio_pin: 23 # TM1638 DIO update_interval: 5s intensity: 5 lambda: |- diff --git a/tests/test6.yaml b/tests/test6.yaml index b0ec04eb6a..2c5aa30aad 100644 --- a/tests/test6.yaml +++ b/tests/test6.yaml @@ -42,7 +42,6 @@ switch: output: pin_4 id: pin_4_switch - spi: # Pins are for SPI1 on the RP2040 Pico-W miso_pin: 8 clk_pin: 10 @@ -50,7 +49,7 @@ spi: # Pins are for SPI1 on the RP2040 Pico-W id: spi_0 interface: hardware -#light: +# light: # - platform: rp2040_pio_led_strip # id: led_strip # pin: GPIO13 @@ -69,7 +68,6 @@ spi: # Pins are for SPI1 on the RP2040 Pico-W # bit1_high: .69us # bit1_low: .4us - sensor: - platform: internal_temperature name: Internal Temperature diff --git a/tests/test8.1.yaml b/tests/test8.1.yaml index bc1d2e22a4..ab3d0d44aa 100644 --- a/tests/test8.1.yaml +++ b/tests/test8.1.yaml @@ -23,12 +23,12 @@ psram: spi: - id: spi_id_1 + type: single clk_pin: - number: GPIO7 + number: GPIO7 allow_other_uses: false mosi_pin: GPIO6 - interface: any - + interface: hardware spi_device: id: spidev data_rate: 2MHz diff --git a/tests/test8.2.yaml b/tests/test8.2.yaml index 69525b333b..ae892559e5 100644 --- a/tests/test8.2.yaml +++ b/tests/test8.2.yaml @@ -24,7 +24,7 @@ psram: spi: - id: spi_id_1 clk_pin: - number: GPIO7 + number: GPIO7 allow_other_uses: false mosi_pin: GPIO6 interface: any diff --git a/tests/test8.yaml b/tests/test8.yaml index 558e86e1f9..5a8ae77468 100644 --- a/tests/test8.yaml +++ b/tests/test8.yaml @@ -52,6 +52,11 @@ spi_device: mode: 3 bit_order: lsb_first +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + display: - platform: ili9xxx id: displ8 @@ -61,6 +66,8 @@ display: reset_pin: number: GPIO48 allow_other_uses: true + lambda: |- + it.printf(10, 100, id(roboto), Color(0x123456), COLOR_OFF, display::TextAlign::BASELINE, "%f", id(heap_free).state); i2c: scl: GPIO18 @@ -85,6 +92,7 @@ binary_sensor: sensor: - platform: debug free: + id: heap_free name: "Heap Free" block: name: "Max Block Free" @@ -92,3 +100,13 @@ sensor: name: "Loop Time" psram: name: "PSRAM Free" + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/test_build_components/build_components_base.esp32-ard.yaml b/tests/test_build_components/build_components_base.esp32-ard.yaml new file mode 100644 index 0000000000..f460c57298 --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-ard.yaml @@ -0,0 +1,20 @@ +esphome: + name: componenttestesp32ard + friendly_name: $component_name + +esp32: + board: nodemcu-32s + framework: + type: arduino + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_name: $component_name + test_name: $test_name + target_platform: $target_platform + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-c3-ard.yaml b/tests/test_build_components/build_components_base.esp32-c3-ard.yaml new file mode 100644 index 0000000000..8a52e0c916 --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-c3-ard.yaml @@ -0,0 +1,20 @@ +esphome: + name: componenttestesp32c3ard + friendly_name: $component_name + +esp32: + board: lolin_c3_mini + framework: + type: arduino + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_name: $component_name + test_name: $test_name + target_platform: $target_platform + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-c3-idf.yaml b/tests/test_build_components/build_components_base.esp32-c3-idf.yaml new file mode 100644 index 0000000000..6b4b61fe58 --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +esphome: + name: componenttestesp32c3idf + friendly_name: $component_name + +esp32: + board: lolin_c3_mini + framework: + type: esp-idf + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_name: $component_name + test_name: $test_name + target_platform: $target_platform + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-idf.yaml b/tests/test_build_components/build_components_base.esp32-idf.yaml new file mode 100644 index 0000000000..ab1bda2a19 --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-idf.yaml @@ -0,0 +1,20 @@ +esphome: + name: componenttestesp32idf + friendly_name: $component_name + +esp32: + board: nodemcu-32s + framework: + type: esp-idf + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_name: $component_name + test_name: $test_name + target_platform: $target_platform + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s2-ard.yaml b/tests/test_build_components/build_components_base.esp32-s2-ard.yaml new file mode 100644 index 0000000000..ffb912d3d9 --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-s2-ard.yaml @@ -0,0 +1,21 @@ +esphome: + name: componenttestesp32s2ard + friendly_name: $component_name + +esp32: + board: esp32-s2-saola-1 + variant: ESP32S2 + framework: + type: arduino + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_name: $component_name + test_name: $test_name + target_platform: $target_platform + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s2-idf.yaml b/tests/test_build_components/build_components_base.esp32-s2-idf.yaml new file mode 100644 index 0000000000..4d1378b2b2 --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-s2-idf.yaml @@ -0,0 +1,21 @@ +esphome: + name: componenttestesp32s2ard + friendly_name: $component_name + +esp32: + board: esp32-s2-saola-1 + variant: ESP32S2 + framework: + type: esp-idf + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_name: $component_name + test_name: $test_name + target_platform: $target_platform + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s3-ard.yaml b/tests/test_build_components/build_components_base.esp32-s3-ard.yaml new file mode 100644 index 0000000000..c850c9665f --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-s3-ard.yaml @@ -0,0 +1,21 @@ +esphome: + name: componenttestesp32s3ard + friendly_name: $component_name + +esp32: + board: esp32s3box + variant: ESP32S3 + framework: + type: arduino + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_name: $component_name + test_name: $test_name + target_platform: $target_platform + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s3-idf.yaml b/tests/test_build_components/build_components_base.esp32-s3-idf.yaml new file mode 100644 index 0000000000..a43a2a6736 --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-s3-idf.yaml @@ -0,0 +1,21 @@ +esphome: + name: componenttestesp32s3ard + friendly_name: $component_name + +esp32: + board: esp32s3box + variant: ESP32S3 + framework: + type: esp-idf + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_name: $component_name + test_name: $test_name + target_platform: $target_platform + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp8266.yaml b/tests/test_build_components/build_components_base.esp8266.yaml new file mode 100644 index 0000000000..d7bdc03659 --- /dev/null +++ b/tests/test_build_components/build_components_base.esp8266.yaml @@ -0,0 +1,18 @@ +esphome: + name: componenttestesp8266 + friendly_name: $component_name + +esp8266: + board: d1_mini + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_name: $component_name + test_name: $test_name + target_platform: $target_platform + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.host.yaml b/tests/test_build_components/build_components_base.host.yaml new file mode 100644 index 0000000000..00b252da2d --- /dev/null +++ b/tests/test_build_components/build_components_base.host.yaml @@ -0,0 +1,18 @@ +esphome: + name: componenttesthost + friendly_name: $component_name + +host: + mac_address: "62:23:45:AF:B3:DD" + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_name: $component_name + test_name: $test_name + target_platform: $target_platform + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.rp2040.yaml b/tests/test_build_components/build_components_base.rp2040.yaml new file mode 100644 index 0000000000..a02942ea35 --- /dev/null +++ b/tests/test_build_components/build_components_base.rp2040.yaml @@ -0,0 +1,21 @@ +esphome: + name: componenttestrp2040 + friendly_name: $component_name + +rp2040: + board: rpipicow + framework: + # Waiting for https://github.com/platformio/platform-raspberrypi/pull/36 + platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_name: $component_name + test_name: $test_name + target_platform: $target_platform + component_test_file: $component_test_file diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index 41d0f3dadb..d61c4a442a 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -8,6 +8,7 @@ If adding unit tests ensure that they are fast. Slower integration tests should not be part of a unit test suite. """ + import sys import pytest diff --git a/tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml b/tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml new file mode 100644 index 0000000000..aaca55b807 --- /dev/null +++ b/tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml @@ -0,0 +1,18 @@ +--- +substitutions: + name: original + +wifi: !include + file: includes/broken_included.yaml.txt + vars: + name: my_custom_ssid + +esphome: + # should be substituted as 'original', + # not overwritten by vars in the !include above + name: ${name} + name_add_mac_suffix: true + platform: esp8266 + board: !include {file: includes/scalar.yaml, vars: {var1: nodemcu}} + + libraries: !include {file: includes/list.yaml, vars: {var1: Wire}} diff --git a/tests/unit_tests/fixtures/yaml_util/includes/broken_included.yaml.txt b/tests/unit_tests/fixtures/yaml_util/includes/broken_included.yaml.txt new file mode 100644 index 0000000000..6e53395c86 --- /dev/null +++ b/tests/unit_tests/fixtures/yaml_util/includes/broken_included.yaml.txt @@ -0,0 +1,5 @@ +--- +# yamllint disable-line + ssid: ${name} +# yamllint disable-line + fdf: error diff --git a/tests/unit_tests/fixtures/yaml_util/missing_comp.yaml b/tests/unit_tests/fixtures/yaml_util/missing_comp.yaml new file mode 100644 index 0000000000..d065901ed9 --- /dev/null +++ b/tests/unit_tests/fixtures/yaml_util/missing_comp.yaml @@ -0,0 +1,12 @@ +esphome: + name: test + +esp32: + board: esp32dev + +wifi: + ap: ~ + +image: + - id: its_a_bug + file: "mdi:bug" diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index efa9ff5677..2860486efe 100644 --- a/tests/unit_tests/test_core.py +++ b/tests/unit_tests/test_core.py @@ -1,7 +1,7 @@ import pytest from hypothesis import given -from hypothesis.provisional import ip_addresses +from hypothesis.strategies import ip_addresses from strategies import mac_addr_strings from esphome import core, const diff --git a/tests/unit_tests/test_helpers.py b/tests/unit_tests/test_helpers.py index 79d39901f0..26ebdcf6af 100644 --- a/tests/unit_tests/test_helpers.py +++ b/tests/unit_tests/test_helpers.py @@ -1,7 +1,7 @@ import pytest from hypothesis import given -from hypothesis.provisional import ip_addresses +from hypothesis.strategies import ip_addresses from esphome import helpers @@ -261,6 +261,7 @@ def test_snake_case(text, expected): ('!"§$%&/()=?foo_bar', "___________foo_bar"), ('foo_!"§$%&/()=?bar', "foo____________bar"), ('foo_bar!"§$%&/()=?', "foo_bar___________"), + ('foo-bar!"§$%&/()=?', "foo-bar___________"), ), ) def test_sanitize(text, expected): diff --git a/tests/unit_tests/test_wizard.py b/tests/unit_tests/test_wizard.py index 46700a3ba8..9260629ec3 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -1,4 +1,5 @@ """Tests for the wizard.py file.""" + import os import esphome.wizard as wz diff --git a/tests/unit_tests/test_yaml_util.py b/tests/unit_tests/test_yaml_util.py index 8ee991f5b3..9178726247 100644 --- a/tests/unit_tests/test_yaml_util.py +++ b/tests/unit_tests/test_yaml_util.py @@ -1,5 +1,6 @@ from esphome import yaml_util from esphome.components import substitutions +from esphome.core import EsphomeError def test_include_with_vars(fixture_path): @@ -11,3 +12,33 @@ def test_include_with_vars(fixture_path): assert actual["esphome"]["libraries"][0] == "Wire" assert actual["esphome"]["board"] == "nodemcu" assert actual["wifi"]["ssid"] == "my_custom_ssid" + + +def test_loading_a_broken_yaml_file(fixture_path): + """Ensure we fallback to pure python to give good errors.""" + yaml_file = fixture_path / "yaml_util" / "broken_includetest.yaml" + + try: + yaml_util.load_yaml(yaml_file) + except EsphomeError as err: + assert "broken_included.yaml" in str(err) + + +def test_loading_a_yaml_file_with_a_missing_component(fixture_path): + """Ensure we show the filename for a yaml file with a missing component.""" + yaml_file = fixture_path / "yaml_util" / "missing_comp.yaml" + + try: + yaml_util.load_yaml(yaml_file) + except EsphomeError as err: + assert "missing_comp.yaml" in str(err) + + +def test_loading_a_missing_file(fixture_path): + """We throw EsphomeError when loading a missing file.""" + yaml_file = fixture_path / "yaml_util" / "missing.yaml" + + try: + yaml_util.load_yaml(yaml_file) + except EsphomeError as err: + assert "missing.yaml" in str(err)