Merge branch 'dev' into add-graphical-layout-system

This commit is contained in:
Michael Davidson 2024-03-27 10:30:00 +11:00 committed by GitHub
commit 5c5fd26dee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
716 changed files with 23743 additions and 2195 deletions

View File

@ -36,7 +36,7 @@ runs:
- name: Build and push to ghcr by digest - name: Build and push to ghcr by digest
id: build-ghcr id: build-ghcr
uses: docker/build-push-action@v5.0.0 uses: docker/build-push-action@v5.3.0
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile
@ -67,7 +67,7 @@ runs:
- name: Build and push to dockerhub by digest - name: Build and push to dockerhub by digest
id: build-dockerhub id: build-dockerhub
uses: docker/build-push-action@v5.0.0 uses: docker/build-push-action@v5.3.0
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile

View File

@ -22,7 +22,7 @@ runs:
python-version: ${{ inputs.python-version }} python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache/restore@v3.3.2 uses: actions/cache/restore@v4.0.2
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length

View File

@ -13,3 +13,13 @@ updates:
schedule: schedule:
interval: daily interval: daily
open-pull-requests-limit: 10 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

81
.github/workflows/ci-api-proto.yml vendored Normal file
View File

@ -0,0 +1,81 @@
name: API Proto CI
# yamllint disable-line rule:truthy
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.0.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.'
});
}
}

View File

@ -46,7 +46,7 @@ jobs:
with: with:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - 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 - name: Set up QEMU
uses: docker/setup-qemu-action@v3.0.0 uses: docker/setup-qemu-action@v3.0.0

View File

@ -12,6 +12,7 @@ on:
- "!.github/workflows/*.yml" - "!.github/workflows/*.yml"
- ".github/workflows/ci.yml" - ".github/workflows/ci.yml"
- "!.yamllint" - "!.yamllint"
- "!.github/dependabot.yml"
merge_group: merge_group:
permissions: permissions:
@ -46,7 +47,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.0.0 uses: actions/cache@v4.0.2
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
@ -366,7 +367,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }} cache-key: ${{ needs.common.outputs.cache-key }}
- name: Cache platformio - name: Cache platformio
uses: actions/cache@v4.0.0 uses: actions/cache@v4.0.2
with: with:
path: ~/.platformio path: ~/.platformio
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
@ -397,6 +398,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- common - common
if: github.event_name == 'pull_request'
outputs: outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }} matrix: ${{ steps.set-matrix.outputs.matrix }}
steps: steps:
@ -405,10 +407,14 @@ jobs:
with: with:
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works. # Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
fetch-depth: 500 fetch-depth: 500
- name: Fetch dev branch - name: Get target branch
id: target-branch
run: | run: |
git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/dev*:refs/remotes/origin/dev* +refs/tags/dev*:refs/tags/dev* echo "branch=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT
git merge-base refs/remotes/origin/dev HEAD - 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 - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@ -418,7 +424,7 @@ jobs:
id: set-matrix id: set-matrix
run: | run: |
. venv/bin/activate . venv/bin/activate
echo "matrix=$(script/list-components.py --changed | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT 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: test-build-components:
name: Component test ${{ matrix.file }} name: Component test ${{ matrix.file }}
@ -426,13 +432,16 @@ jobs:
needs: needs:
- common - common
- list-components - list-components
if: ${{ needs.list-components.outputs.matrix != '[]' && needs.list-components.outputs.matrix != '' }} if: ${{ github.event_name == 'pull_request' && needs.list-components.outputs.matrix != '[]' && needs.list-components.outputs.matrix != '' }}
strategy: strategy:
fail-fast: false fail-fast: false
max-parallel: 2 max-parallel: 2
matrix: matrix:
file: ${{ fromJson(needs.list-components.outputs.matrix) }} file: ${{ fromJson(needs.list-components.outputs.matrix) }}
steps: steps:
- name: Install libsodium
run: sudo apt-get install libsodium-dev
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
- name: Restore Python - name: Restore Python

View File

@ -85,18 +85,18 @@ jobs:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - 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 - name: Set up QEMU
if: matrix.platform != 'linux/amd64' if: matrix.platform != 'linux/amd64'
uses: docker/setup-qemu-action@v3.0.0 uses: docker/setup-qemu-action@v3.0.0
- name: Log in to docker hub - name: Log in to docker hub
uses: docker/login-action@v3.0.0 uses: docker/login-action@v3.1.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry - name: Log in to the GitHub container registry
uses: docker/login-action@v3.0.0 uses: docker/login-action@v3.1.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@ -163,17 +163,17 @@ jobs:
name: digests-${{ matrix.image.target }}-${{ matrix.registry }} name: digests-${{ matrix.image.target }}-${{ matrix.registry }}
path: /tmp/digests path: /tmp/digests
- name: Set up Docker Buildx - 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 - name: Log in to docker hub
if: matrix.registry == 'dockerhub' if: matrix.registry == 'dockerhub'
uses: docker/login-action@v3.0.0 uses: docker/login-action@v3.1.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry - name: Log in to the GitHub container registry
if: matrix.registry == 'ghcr' if: matrix.registry == 'ghcr'
uses: docker/login-action@v3.0.0 uses: docker/login-action@v3.1.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}

View File

@ -37,7 +37,7 @@ jobs:
python ./script/sync-device_class.py python ./script/sync-device_class.py
- name: Commit changes - name: Commit changes
uses: peter-evans/create-pull-request@v6.0.0 uses: peter-evans/create-pull-request@v6.0.2
with: with:
commit-message: "Synchronise Device Classes from Home Assistant" commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@nabucasa.com> committer: esphomebot <esphome@nabucasa.com>

View File

@ -22,12 +22,15 @@ esphome/components/ade7880/* @kpfleming
esphome/components/ade7953/* @angelnu esphome/components/ade7953/* @angelnu
esphome/components/ade7953_i2c/* @angelnu esphome/components/ade7953_i2c/* @angelnu
esphome/components/ade7953_spi/* @angelnu esphome/components/ade7953_spi/* @angelnu
esphome/components/ads1118/* @solomondg1
esphome/components/ags10/* @mak-42
esphome/components/airthings_ble/* @jeromelaban esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/alarm_control_panel/* @grahambrown11 @hwstar esphome/components/alarm_control_panel/* @grahambrown11 @hwstar
esphome/components/alpha3/* @jan-hofmeier esphome/components/alpha3/* @jan-hofmeier
esphome/components/am2315c/* @swoboda1337
esphome/components/am43/* @buxtronix esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix esphome/components/am43/cover/* @buxtronix
esphome/components/am43/sensor/* @buxtronix esphome/components/am43/sensor/* @buxtronix
@ -39,6 +42,7 @@ esphome/components/as5600/* @ammmze
esphome/components/as5600/sensor/* @ammmze esphome/components/as5600/sensor/* @ammmze
esphome/components/as7341/* @mrgnr esphome/components/as7341/* @mrgnr
esphome/components/async_tcp/* @OttoWinter esphome/components/async_tcp/* @OttoWinter
esphome/components/at581x/* @X-Ryl669
esphome/components/atc_mithermometer/* @ahpohl esphome/components/atc_mithermometer/* @ahpohl
esphome/components/atm90e26/* @danieltwagner esphome/components/atm90e26/* @danieltwagner
esphome/components/b_parasite/* @rbaron esphome/components/b_parasite/* @rbaron
@ -78,12 +82,15 @@ esphome/components/copy/* @OttoWinter
esphome/components/cover/* @esphome/core esphome/components/cover/* @esphome/core
esphome/components/cs5460a/* @balrog-kun esphome/components/cs5460a/* @balrog-kun
esphome/components/cse7761/* @berfenger esphome/components/cse7761/* @berfenger
esphome/components/cst226/* @clydebarrow
esphome/components/cst816/* @clydebarrow
esphome/components/ct_clamp/* @jesserockz esphome/components/ct_clamp/* @jesserockz
esphome/components/current_based/* @djwmarcx esphome/components/current_based/* @djwmarcx
esphome/components/dac7678/* @NickB1 esphome/components/dac7678/* @NickB1
esphome/components/daikin_brc/* @hagak esphome/components/daikin_brc/* @hagak
esphome/components/daly_bms/* @s1lvi0 esphome/components/daly_bms/* @s1lvi0
esphome/components/dashboard_import/* @esphome/core esphome/components/dashboard_import/* @esphome/core
esphome/components/datetime/* @rfdarter
esphome/components/debug/* @OttoWinter esphome/components/debug/* @OttoWinter
esphome/components/delonghi/* @grob6000 esphome/components/delonghi/* @grob6000
esphome/components/dfplayer/* @glmnet esphome/components/dfplayer/* @glmnet
@ -97,6 +104,7 @@ esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M esphome/components/ee895/* @Stock-M
esphome/components/ektf2232/touchscreen/* @jesserockz esphome/components/ektf2232/touchscreen/* @jesserockz
esphome/components/emc2101/* @ellull esphome/components/emc2101/* @ellull
esphome/components/emmeti/* @E440QF
esphome/components/ens160/* @vincentscode esphome/components/ens160/* @vincentscode
esphome/components/ens210/* @itn3rd77 esphome/components/ens210/* @itn3rd77
esphome/components/esp32/* @esphome/core esphome/components/esp32/* @esphome/core
@ -115,7 +123,8 @@ esphome/components/ezo_pmp/* @carlos-sarmiento
esphome/components/factory_reset/* @anatoly-savchenkov esphome/components/factory_reset/* @anatoly-savchenkov
esphome/components/fastled_base/* @OttoWinter esphome/components/fastled_base/* @OttoWinter
esphome/components/feedback/* @ianchi 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/fs3000/* @kahrendt
esphome/components/ft5x06/* @clydebarrow esphome/components/ft5x06/* @clydebarrow
esphome/components/ft63x6/* @gpambrozio esphome/components/ft63x6/* @gpambrozio
@ -146,6 +155,7 @@ esphome/components/honeywellabp2_i2c/* @jpfaff
esphome/components/host/* @esphome/core esphome/components/host/* @esphome/core
esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/hte501/* @Stock-M esphome/components/hte501/* @Stock-M
esphome/components/htu31d/* @betterengineering
esphome/components/hydreon_rgxx/* @functionpointer esphome/components/hydreon_rgxx/* @functionpointer
esphome/components/hyt271/* @Philippe12 esphome/components/hyt271/* @Philippe12
esphome/components/i2c/* @esphome/core esphome/components/i2c/* @esphome/core
@ -165,6 +175,7 @@ esphome/components/integration/* @OttoWinter
esphome/components/internal_temperature/* @Mat931 esphome/components/internal_temperature/* @Mat931
esphome/components/interval/* @esphome/core esphome/components/interval/* @esphome/core
esphome/components/json/* @OttoWinter esphome/components/json/* @OttoWinter
esphome/components/kamstrup_kmp/* @cfeenstra1024
esphome/components/key_collector/* @ssieb esphome/components/key_collector/* @ssieb
esphome/components/key_provider/* @ssieb esphome/components/key_provider/* @ssieb
esphome/components/kuntze/* @ssieb esphome/components/kuntze/* @ssieb
@ -226,6 +237,7 @@ esphome/components/mopeka_pro_check/* @spbrogan
esphome/components/mopeka_std_check/* @Fabian-Schmidt esphome/components/mopeka_std_check/* @Fabian-Schmidt
esphome/components/mpl3115a2/* @kbickar esphome/components/mpl3115a2/* @kbickar
esphome/components/mpu6886/* @fabaff esphome/components/mpu6886/* @fabaff
esphome/components/ms8607/* @e28eta
esphome/components/network/* @esphome/core esphome/components/network/* @esphome/core
esphome/components/nextion/* @senexcrenshaw esphome/components/nextion/* @senexcrenshaw
esphome/components/nextion/binary_sensor/* @senexcrenshaw esphome/components/nextion/binary_sensor/* @senexcrenshaw
@ -262,6 +274,7 @@ esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/pylontech/* @functionpointer esphome/components/pylontech/* @functionpointer
esphome/components/qmp6988/* @andrewpc esphome/components/qmp6988/* @andrewpc
esphome/components/qr_code/* @wjtje esphome/components/qr_code/* @wjtje
esphome/components/qspi_amoled/* @clydebarrow
esphome/components/qwiic_pir/* @kahrendt esphome/components/qwiic_pir/* @kahrendt
esphome/components/radon_eye_ble/* @jeffeb3 esphome/components/radon_eye_ble/* @jeffeb3
esphome/components/radon_eye_rd200/* @jeffeb3 esphome/components/radon_eye_rd200/* @jeffeb3
@ -275,6 +288,7 @@ esphome/components/rgbct/* @jesserockz
esphome/components/rp2040/* @jesserockz esphome/components/rp2040/* @jesserockz
esphome/components/rp2040_pio_led_strip/* @Papa-DMan esphome/components/rp2040_pio_led_strip/* @Papa-DMan
esphome/components/rp2040_pwm/* @jesserockz esphome/components/rp2040_pwm/* @jesserockz
esphome/components/rpi_dpi_rgb/* @clydebarrow
esphome/components/rtl87xx/* @kuba2k2 esphome/components/rtl87xx/* @kuba2k2
esphome/components/rtttl/* @glmnet esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @jsuanet @paulmonigatti esphome/components/safe_mode/* @jsuanet @paulmonigatti
@ -282,6 +296,7 @@ esphome/components/scd4x/* @martgras @sjtrny
esphome/components/script/* @esphome/core esphome/components/script/* @esphome/core
esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sdp3x/* @Azimath esphome/components/sdp3x/* @Azimath
esphome/components/seeed_mr24hpc1/* @limengdu
esphome/components/selec_meter/* @sourabhjaiswal esphome/components/selec_meter/* @sourabhjaiswal
esphome/components/select/* @esphome/core esphome/components/select/* @esphome/core
esphome/components/sen0321/* @notjj esphome/components/sen0321/* @notjj
@ -326,11 +341,13 @@ esphome/components/ssd1351_spi/* @kbx81
esphome/components/st7567_base/* @latonita esphome/components/st7567_base/* @latonita
esphome/components/st7567_i2c/* @latonita esphome/components/st7567_i2c/* @latonita
esphome/components/st7567_spi/* @latonita esphome/components/st7567_spi/* @latonita
esphome/components/st7701s/* @clydebarrow
esphome/components/st7735/* @SenexCrenshaw esphome/components/st7735/* @SenexCrenshaw
esphome/components/st7789v/* @kbx81 esphome/components/st7789v/* @kbx81
esphome/components/st7920/* @marsjan155 esphome/components/st7920/* @marsjan155
esphome/components/substitutions/* @esphome/core esphome/components/substitutions/* @esphome/core
esphome/components/sun/* @OttoWinter esphome/components/sun/* @OttoWinter
esphome/components/sun_gtil2/* @Mat931
esphome/components/switch/* @esphome/core esphome/components/switch/* @esphome/core
esphome/components/t6615/* @tylermenezes esphome/components/t6615/* @tylermenezes
esphome/components/tca9548a/* @andreashergert1984 esphome/components/tca9548a/* @andreashergert1984
@ -338,6 +355,8 @@ esphome/components/tcl112/* @glmnet
esphome/components/tee501/* @Stock-M esphome/components/tee501/* @Stock-M
esphome/components/teleinfo/* @0hax esphome/components/teleinfo/* @0hax
esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
esphome/components/template/datetime/* @rfdarter
esphome/components/template/fan/* @ssieb
esphome/components/text/* @mauritskorse esphome/components/text/* @mauritskorse
esphome/components/thermostat/* @kbx81 esphome/components/thermostat/* @kbx81
esphome/components/time/* @OttoWinter esphome/components/time/* @OttoWinter
@ -369,6 +388,7 @@ esphome/components/ultrasonic/* @OttoWinter
esphome/components/uponor_smatrix/* @kroimon esphome/components/uponor_smatrix/* @kroimon
esphome/components/vbus/* @ssieb esphome/components/vbus/* @ssieb
esphome/components/veml3235/* @kbx81 esphome/components/veml3235/* @kbx81
esphome/components/veml7700/* @latonita
esphome/components/version/* @esphome/core esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @willwill2will54 esphome/components/wake_on_lan/* @willwill2will54

View File

@ -21,4 +21,10 @@ export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages" export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache" 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 "$@" exec esphome "$@"

View File

@ -297,8 +297,27 @@ def upload_using_platformio(config, port):
return platformio_api.run_platformio_cli_run(config, CORE.verbose, *upload_args) 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): def upload_program(config, args, host):
if get_port_type(host) == "SERIAL": if get_port_type(host) == "SERIAL":
check_permissions(host)
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266): if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
file = getattr(args, "file", None) file = getattr(args, "file", None)
return upload_using_esptool(config, host, file) return upload_using_esptool(config, host, file)
@ -344,6 +363,7 @@ def show_logs(config, args, port):
if "logger" not in config: if "logger" not in config:
raise EsphomeError("Logger is not configured!") raise EsphomeError("Logger is not configured!")
if get_port_type(port) == "SERIAL": if get_port_type(port) == "SERIAL":
check_permissions(port)
return run_miniterm(config, port) return run_miniterm(config, port)
if get_port_type(port) == "NETWORK" and "api" in config: if get_port_type(port) == "NETWORK" and "api" in config:
if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config: if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:

View File

@ -87,4 +87,5 @@ from esphome.cpp_types import ( # noqa
gpio_Flags, gpio_Flags,
EntityCategory, EntityCategory,
Parented, Parented,
ESPTime,
) )

View File

@ -41,6 +41,7 @@ CONF_CURRENT_GAIN_A = "current_gain_a"
CONF_CURRENT_GAIN_B = "current_gain_b" CONF_CURRENT_GAIN_B = "current_gain_b"
CONF_ACTIVE_POWER_GAIN_A = "active_power_gain_a" CONF_ACTIVE_POWER_GAIN_A = "active_power_gain_a"
CONF_ACTIVE_POWER_GAIN_B = "active_power_gain_b" CONF_ACTIVE_POWER_GAIN_B = "active_power_gain_b"
CONF_USE_ACCUMULATED_ENERGY_REGISTERS = "use_accumulated_energy_registers"
PGA_GAINS = { PGA_GAINS = {
"1x": 0b000, "1x": 0b000,
"2x": 0b001, "2x": 0b001,
@ -155,6 +156,7 @@ ADE7953_CONFIG_SCHEMA = cv.Schema(
cv.Optional(CONF_ACTIVE_POWER_GAIN_B, default=0x400000): cv.hex_int_range( cv.Optional(CONF_ACTIVE_POWER_GAIN_B, default=0x400000): cv.hex_int_range(
min=0x100000, max=0x800000 min=0x100000, max=0x800000
), ),
cv.Optional(CONF_USE_ACCUMULATED_ENERGY_REGISTERS, default=False): cv.boolean,
} }
).extend(cv.polling_component_schema("60s")) ).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_bigain(config.get(CONF_CURRENT_GAIN_B)))
cg.add(var.set_awgain(config.get(CONF_ACTIVE_POWER_GAIN_A))) 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_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 [ for key in [
CONF_VOLTAGE, CONF_VOLTAGE,

View File

@ -6,6 +6,9 @@ namespace ade7953_base {
static const char *const TAG = "ade7953"; 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() { void ADE7953::setup() {
if (this->irq_pin_ != nullptr) { if (this->irq_pin_ != nullptr) {
this->irq_pin_->setup(); this->irq_pin_->setup();
@ -34,6 +37,7 @@ void ADE7953::setup() {
this->ade_read_32(BIGAIN_32, &bigain_); this->ade_read_32(BIGAIN_32, &bigain_);
this->ade_read_32(AWGAIN_32, &awgain_); this->ade_read_32(AWGAIN_32, &awgain_);
this->ade_read_32(BWGAIN_32, &bwgain_); this->ade_read_32(BWGAIN_32, &bwgain_);
this->last_update_ = millis();
this->is_setup_ = true; 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(" ", "Active Power B Sensor", this->active_power_b_sensor_);
LOG_SENSOR(" ", "Rective Power A Sensor", this->reactive_power_a_sensor_); LOG_SENSOR(" ", "Rective Power A Sensor", this->reactive_power_a_sensor_);
LOG_SENSOR(" ", "Reactive Power B Sensor", this->reactive_power_b_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_V_8: 0x%X", pga_v_);
ESP_LOGCONFIG(TAG, " PGA_IA_8: 0x%X", pga_ia_); ESP_LOGCONFIG(TAG, " PGA_IA_8: 0x%X", pga_ia_);
ESP_LOGCONFIG(TAG, " PGA_IB_8: 0x%X", pga_ib_); ESP_LOGCONFIG(TAG, " PGA_IB_8: 0x%X", pga_ib_);
@ -85,6 +90,7 @@ void ADE7953::update() {
uint32_t val; uint32_t val;
uint16_t val_16; uint16_t val_16;
uint16_t reg;
// Power factor // Power factor
err = this->ade_read_16(0x010A, &val_16); err = this->ade_read_16(0x010A, &val_16);
@ -92,23 +98,36 @@ void ADE7953::update() {
err = this->ade_read_16(0x010B, &val_16); err = this->ade_read_16(0x010B, &val_16);
ADE_PUBLISH(power_factor_b, (int16_t) val_16, (0x7FFF / 100.0f)); 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 // Apparent power
err = this->ade_read_32(0x0310, &val); reg = this->use_acc_energy_regs_ ? 0x0322 : 0x0310;
ADE_PUBLISH(apparent_power_a, (int32_t) val, 154.0f); err = this->ade_read_32(reg, &val);
err = this->ade_read_32(0x0311, &val); ADE_PUBLISH(apparent_power_a, (int32_t) val, pf);
ADE_PUBLISH(apparent_power_b, (int32_t) val, 154.0f); err = this->ade_read_32(reg + 1, &val);
ADE_PUBLISH(apparent_power_b, (int32_t) val, pf);
// Active power // Active power
err = this->ade_read_32(0x0312, &val); reg = this->use_acc_energy_regs_ ? 0x031E : 0x0312;
ADE_PUBLISH(active_power_a, (int32_t) val, 154.0f); err = this->ade_read_32(reg, &val);
err = this->ade_read_32(0x0313, &val); ADE_PUBLISH(active_power_a, (int32_t) val, pf);
ADE_PUBLISH(active_power_b, (int32_t) val, 154.0f); err = this->ade_read_32(reg + 1, &val);
ADE_PUBLISH(active_power_b, (int32_t) val, pf);
// Reactive power // Reactive power
err = this->ade_read_32(0x0314, &val); reg = this->use_acc_energy_regs_ ? 0x0320 : 0x0314;
ADE_PUBLISH(reactive_power_a, (int32_t) val, 154.0f); err = this->ade_read_32(reg, &val);
err = this->ade_read_32(0x0315, &val); ADE_PUBLISH(reactive_power_a, (int32_t) val, pf);
ADE_PUBLISH(reactive_power_b, (int32_t) val, 154.0f); err = this->ade_read_32(reg + 1, &val);
ADE_PUBLISH(reactive_power_b, (int32_t) val, pf);
// Current // Current
err = this->ade_read_32(0x031A, &val); err = this->ade_read_32(0x031A, &val);

View File

@ -52,6 +52,8 @@ class ADE7953 : public PollingComponent, public sensor::Sensor {
void set_awgain(uint32_t awgain) { awgain_ = awgain; } void set_awgain(uint32_t awgain) { awgain_ = awgain; }
void set_bwgain(uint32_t bwgain) { bwgain_ = bwgain; } 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_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_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 bigain_;
uint32_t awgain_; uint32_t awgain_;
uint32_t bwgain_; 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; virtual bool ade_write_8(uint16_t reg, uint8_t value) = 0;

View File

@ -13,29 +13,29 @@ void AdE7953I2c::dump_config() {
ade7953_base::ADE7953::dump_config(); ade7953_base::ADE7953::dump_config();
} }
bool AdE7953I2c::ade_write_8(uint16_t reg, uint8_t value) { bool AdE7953I2c::ade_write_8(uint16_t reg, uint8_t value) {
std::vector<uint8_t> data(3); uint8_t data[3];
data.push_back(reg >> 8); data[0] = reg >> 8;
data.push_back(reg >> 0); data[1] = reg >> 0;
data.push_back(value); data[2] = value;
return this->write(data.data(), data.size()) != i2c::ERROR_OK; return this->write(data, 3) != i2c::ERROR_OK;
} }
bool AdE7953I2c::ade_write_16(uint16_t reg, uint16_t value) { bool AdE7953I2c::ade_write_16(uint16_t reg, uint16_t value) {
std::vector<uint8_t> data(4); uint8_t data[4];
data.push_back(reg >> 8); data[0] = reg >> 8;
data.push_back(reg >> 0); data[1] = reg >> 0;
data.push_back(value >> 8); data[2] = value >> 8;
data.push_back(value >> 0); data[3] = value >> 0;
return this->write(data.data(), data.size()) != i2c::ERROR_OK; return this->write(data, 4) != i2c::ERROR_OK;
} }
bool AdE7953I2c::ade_write_32(uint16_t reg, uint32_t value) { bool AdE7953I2c::ade_write_32(uint16_t reg, uint32_t value) {
std::vector<uint8_t> data(6); uint8_t data[6];
data.push_back(reg >> 8); data[0] = reg >> 8;
data.push_back(reg >> 0); data[1] = reg >> 0;
data.push_back(value >> 24); data[2] = value >> 24;
data.push_back(value >> 16); data[3] = value >> 16;
data.push_back(value >> 8); data[4] = value >> 8;
data.push_back(value >> 0); data[5] = value >> 0;
return this->write(data.data(), data.size()) != i2c::ERROR_OK; return this->write(data, 6) != i2c::ERROR_OK;
} }
bool AdE7953I2c::ade_read_8(uint16_t reg, uint8_t *value) { bool AdE7953I2c::ade_read_8(uint16_t reg, uint8_t *value) {
uint8_t reg_data[2]; uint8_t reg_data[2];

View File

@ -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)

View File

@ -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<int16_t>(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

View File

@ -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<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_TRAILING,
spi::DATA_RATE_1MHZ> {
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

View File

@ -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))

View File

@ -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

View File

@ -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<ADS1118> {
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

View File

@ -0,0 +1 @@
CODEOWNERS = ["@mak-42"]

View File

@ -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<uint8_t, 5> 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<uint8_t, 5> 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<uint32_t> 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<uint8_t> AGS10Component::read_version_() {
auto data = this->read_and_check_<5>(REG_VERSION);
if (data) {
auto res = *data;
return res[3];
}
return nullopt;
}
optional<uint32_t> 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<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_check_(uint8_t a_register) {
auto data = this->read_bytes<N>(a_register);
if (!data.has_value()) {
this->error_code_ = COMMUNICATION_FAILED;
ESP_LOGE(TAG, "Reading AGS10 version failed!");
return optional<std::array<uint8_t, N>>();
}
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<std::array<uint8_t, N>>();
}
return data;
}
template<size_t N> uint8_t AGS10Component::calc_crc8_(std::array<uint8_t, N> 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

View File

@ -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<uint32_t> read_tvoc_();
/**
* Reads and returns a firmware version of AGS10.
*/
optional<uint8_t> read_version_();
/**
* Reads and returns the resistance of AGS10.
*/
optional<uint32_t> read_resistance_();
/**
* Read, checks and returns data from the sensor.
*/
template<size_t N> optional<std::array<uint8_t, N>> 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<size_t N> uint8_t calc_crc8_(std::array<uint8_t, N> dat, uint8_t num);
};
template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {
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<typename... Ts> class AGS10SetZeroPointAction : public Action<Ts...>, public Parented<AGS10Component> {
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

View File

@ -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

View File

@ -15,36 +15,43 @@
#include "aht10.h" #include "aht10.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include <cinttypes>
namespace esphome { namespace esphome {
namespace aht10 { namespace aht10 {
static const char *const TAG = "aht10"; static const char *const TAG = "aht10";
static const size_t SIZE_CALIBRATE_CMD = 3; static const uint8_t AHT10_INITIALIZE_CMD[] = {0xE1, 0x08, 0x00};
static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1, 0x08, 0x00}; static const uint8_t AHT20_INITIALIZE_CMD[] = {0xBE, 0x08, 0x00};
static const uint8_t AHT20_CALIBRATE_CMD[] = {0xBE, 0x08, 0x00};
static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 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_SOFTRESET_CMD[] = {0xBA};
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_DEFAULT_DELAY = 5; // ms, for initialization and temperature measurement
static const uint8_t AHT10_CAL_ATTEMPTS = 10; 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; static const uint8_t AHT10_STATUS_BUSY = 0x80;
void AHT10Component::setup() { 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_) { switch (this->variant_) {
case AHT10Variant::AHT20: case AHT10Variant::AHT20:
calibrate_cmd = AHT20_CALIBRATE_CMD;
ESP_LOGCONFIG(TAG, "Setting up AHT20"); ESP_LOGCONFIG(TAG, "Setting up AHT20");
error_code = this->write(AHT20_INITIALIZE_CMD, sizeof(AHT20_INITIALIZE_CMD));
break; break;
case AHT10Variant::AHT10: case AHT10Variant::AHT10:
default:
calibrate_cmd = AHT10_CALIBRATE_CMD;
ESP_LOGCONFIG(TAG, "Setting up AHT10"); ESP_LOGCONFIG(TAG, "Setting up AHT10");
error_code = this->write(AHT10_INITIALIZE_CMD, sizeof(AHT10_INITIALIZE_CMD));
break;
} }
if (error_code != i2c::ERROR_OK) {
if (this->write(calibrate_cmd, SIZE_CALIBRATE_CMD) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with AHT10 failed!"); ESP_LOGE(TAG, "Communication with AHT10 failed!");
this->mark_failed(); this->mark_failed();
return; return;
@ -59,89 +66,92 @@ void AHT10Component::setup() {
return; return;
} }
++cal_attempts; ++cal_attempts;
if (cal_attempts > AHT10_CAL_ATTEMPTS) { if (cal_attempts > AHT10_INIT_ATTEMPTS) {
ESP_LOGE(TAG, "AHT10 calibration timed out!"); ESP_LOGE(TAG, "AHT10 initialization timed out!");
this->mark_failed(); this->mark_failed();
return; return;
} }
} }
if ((data & 0x68) != 0x08) { // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED 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(); this->mark_failed();
return; return;
} }
ESP_LOGV(TAG, "AHT10 calibrated"); ESP_LOGV(TAG, "AHT10 initialization");
} }
void AHT10Component::update() { void AHT10Component::restart_read_() {
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) { if (this->read_count_ == AHT10_ATTEMPTS) {
ESP_LOGE(TAG, "Communication with AHT10 failed!"); this->read_count_ = 0;
this->status_set_warning(); this->status_set_error("Measurements reading timed-out!");
return; return;
} }
this->read_count_++;
this->set_timeout(AHT10_READ_DELAY, [this]() { this->read_data_(); });
}
void AHT10Component::read_data_() {
uint8_t data[6]; uint8_t data[6];
uint8_t delay_ms = AHT10_DEFAULT_DELAY; if (this->read_count_ > 1)
if (this->humidity_sensor_ != nullptr) ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
delay_ms = AHT10_HUMIDITY_DELAY; if (this->read(data, 6) != i2c::ERROR_OK) {
bool success = false; this->status_set_warning("AHT10 read failed, retrying soon");
for (int i = 0; i < AHT10_ATTEMPTS; ++i) { this->restart_read_();
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();
return; 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_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; 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) { if (this->temperature_sensor_ != nullptr) {
float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
this->temperature_sensor_->publish_state(temperature); this->temperature_sensor_->publish_state(temperature);
} }
if (this->humidity_sensor_ != nullptr) { 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)) { if (std::isnan(humidity)) {
ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum"); ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
} }
this->humidity_sensor_->publish_state(humidity); this->humidity_sensor_->publish_state(humidity);
} }
this->status_clear_warning(); 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; } float AHT10Component::get_setup_priority() const { return setup_priority::DATA; }

View File

@ -26,6 +26,10 @@ class AHT10Component : public PollingComponent, public i2c::I2CDevice {
sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr};
AHT10Variant variant_{}; AHT10Variant variant_{};
unsigned read_count_{};
void read_data_();
void restart_read_();
uint32_t start_time_{};
}; };
} // namespace aht10 } // namespace aht10

View File

@ -0,0 +1 @@
CODEOWNERS = ["@swoboda1337"]

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -44,6 +44,7 @@ service APIConnection {
rpc button_command (ButtonCommandRequest) returns (void) {} rpc button_command (ButtonCommandRequest) returns (void) {}
rpc lock_command (LockCommandRequest) returns (void) {} rpc lock_command (LockCommandRequest) returns (void) {}
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {} rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
rpc date_command (DateCommandRequest) returns (void) {}
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {} rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
@ -600,6 +601,7 @@ message ListEntitiesTextSensorResponse {
string icon = 5; string icon = 5;
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
string device_class = 8;
} }
message TextSensorStateResponse { message TextSensorStateResponse {
option (id) = 27; option (id) = 27;
@ -1449,6 +1451,7 @@ message VoiceAssistantRequest {
string conversation_id = 2; string conversation_id = 2;
uint32 flags = 3; uint32 flags = 3;
VoiceAssistantAudioSettings audio_settings = 4; VoiceAssistantAudioSettings audio_settings = 4;
string wake_word_phrase = 5;
} }
message VoiceAssistantResponse { message VoiceAssistantResponse {
@ -1596,3 +1599,45 @@ message TextCommandRequest {
fixed32 key = 1; fixed32 key = 1;
string state = 2; 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;
}

View File

@ -543,6 +543,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
msg.icon = text_sensor->get_icon(); msg.icon = text_sensor->get_icon();
msg.disabled_by_default = text_sensor->is_disabled_by_default(); msg.disabled_by_default = text_sensor->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category());
msg.device_class = text_sensor->get_device_class();
return this->send_list_entities_text_sensor_response(msg); return this->send_list_entities_text_sensor_response(msg);
} }
#endif #endif
@ -697,6 +698,43 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
} }
#endif #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<enums::EntityCategory>(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 #ifdef USE_TEXT
bool APIConnection::send_text_state(text::Text *text, std::string state) { bool APIConnection::send_text_state(text::Text *text, std::string state) {
if (!this->state_subscription_) if (!this->state_subscription_)

View File

@ -72,6 +72,11 @@ class APIConnection : public APIServerConnection {
bool send_number_info(number::Number *number); bool send_number_info(number::Number *number);
void number_command(const NumberCommandRequest &msg) override; void number_command(const NumberCommandRequest &msg) override;
#endif #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 #ifdef USE_TEXT
bool send_text_state(text::Text *text, std::string state); bool send_text_state(text::Text *text, std::string state);
bool send_text_info(text::Text *text); bool send_text_info(text::Text *text);

View File

@ -2721,6 +2721,10 @@ bool ListEntitiesTextSensorResponse::decode_length(uint32_t field_id, ProtoLengt
this->icon = value.as_string(); this->icon = value.as_string();
return true; return true;
} }
case 8: {
this->device_class = value.as_string();
return true;
}
default: default:
return false; return false;
} }
@ -2743,6 +2747,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon); buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default); buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_string(8, this->device_class);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
@ -2776,6 +2781,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
out.append(" entity_category: "); out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n"); out.append("\n");
out.append(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -6594,6 +6603,10 @@ bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimite
this->audio_settings = value.as_message<VoiceAssistantAudioSettings>(); this->audio_settings = value.as_message<VoiceAssistantAudioSettings>();
return true; return true;
} }
case 5: {
this->wake_word_phrase = value.as_string();
return true;
}
default: default:
return false; return false;
} }
@ -6603,6 +6616,7 @@ void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(2, this->conversation_id); buffer.encode_string(2, this->conversation_id);
buffer.encode_uint32(3, this->flags); buffer.encode_uint32(3, this->flags);
buffer.encode_message<VoiceAssistantAudioSettings>(4, this->audio_settings); buffer.encode_message<VoiceAssistantAudioSettings>(4, this->audio_settings);
buffer.encode_string(5, this->wake_word_phrase);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantRequest::dump_to(std::string &out) const { void VoiceAssistantRequest::dump_to(std::string &out) const {
@ -6624,6 +6638,10 @@ void VoiceAssistantRequest::dump_to(std::string &out) const {
out.append(" audio_settings: "); out.append(" audio_settings: ");
this->audio_settings.dump_to(out); this->audio_settings.dump_to(out);
out.append("\n"); out.append("\n");
out.append(" wake_word_phrase: ");
out.append("'").append(this->wake_word_phrase).append("'");
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -7166,6 +7184,225 @@ void TextCommandRequest::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #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<enums::EntityCategory>();
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<enums::EntityCategory>(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<enums::EntityCategory>(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 api
} // namespace esphome } // namespace esphome

View File

@ -713,6 +713,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
std::string icon{}; std::string icon{};
bool disabled_by_default{false}; bool disabled_by_default{false};
enums::EntityCategory entity_category{}; enums::EntityCategory entity_category{};
std::string device_class{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -1701,6 +1702,7 @@ class VoiceAssistantRequest : public ProtoMessage {
std::string conversation_id{}; std::string conversation_id{};
uint32_t flags{0}; uint32_t flags{0};
VoiceAssistantAudioSettings audio_settings{}; VoiceAssistantAudioSettings audio_settings{};
std::string wake_word_phrase{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -1848,6 +1850,56 @@ class TextCommandRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited 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 api
} // namespace esphome } // namespace esphome

View File

@ -513,6 +513,24 @@ bool APIServerConnectionBase::send_text_state_response(const TextStateResponse &
#endif #endif
#ifdef USE_TEXT #ifdef USE_TEXT
#endif #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_<ListEntitiesDateResponse>(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_<DateStateResponse>(msg, 101);
}
#endif
#ifdef USE_DATETIME_DATE
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) { switch (msg_type) {
case 1: { case 1: {
@ -942,6 +960,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_text_command_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_text_command_request: %s", msg.dump().c_str());
#endif #endif
this->on_text_command_request(msg); 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 #endif
break; break;
} }
@ -1218,6 +1247,19 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma
this->media_player_command(msg); this->media_player_command(msg);
} }
#endif #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 #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &msg) { const SubscribeBluetoothLEAdvertisementsRequest &msg) {

View File

@ -257,6 +257,15 @@ class APIServerConnectionBase : public ProtoService {
#endif #endif
#ifdef USE_TEXT #ifdef USE_TEXT
virtual void on_text_command_request(const TextCommandRequest &value){}; 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 #endif
protected: protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@ -312,6 +321,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
#endif #endif
#ifdef USE_DATETIME_DATE
virtual void date_command(const DateCommandRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif #endif
@ -398,6 +410,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
#endif #endif
#ifdef USE_DATETIME_DATE
void on_date_command_request(const DateCommandRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif #endif

View File

@ -255,6 +255,15 @@ void APIServer::on_number_update(number::Number *obj, float state) {
} }
#endif #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 #ifdef USE_TEXT
void APIServer::on_text_update(text::Text *obj, const std::string &state) { void APIServer::on_text_update(text::Text *obj, const std::string &state) {
if (obj->is_internal()) if (obj->is_internal())

View File

@ -66,6 +66,9 @@ class APIServer : public Component, public Controller {
#ifdef USE_NUMBER #ifdef USE_NUMBER
void on_number_update(number::Number *obj, float state) override; void on_number_update(number::Number *obj, float state) override;
#endif #endif
#ifdef USE_DATETIME_DATE
void on_date_update(datetime::DateEntity *obj) override;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
void on_text_update(text::Text *obj, const std::string &state) override; void on_text_update(text::Text *obj, const std::string &state) override;
#endif #endif

View File

@ -1,8 +1,8 @@
#include "list_entities.h" #include "list_entities.h"
#include "esphome/core/util.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "api_connection.h" #include "api_connection.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
namespace esphome { namespace esphome {
namespace api { 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); } bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); }
#endif #endif
#ifdef USE_DATETIME_DATE
bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_info(date); }
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); } bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); }
#endif #endif

View File

@ -46,6 +46,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_NUMBER #ifdef USE_NUMBER
bool on_number(number::Number *number) override; bool on_number(number::Number *number) override;
#endif #endif
#ifdef USE_DATETIME_DATE
bool on_date(datetime::DateEntity *date) override;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool on_text(text::Text *text) override; bool on_text(text::Text *text) override;
#endif #endif

View File

@ -42,6 +42,9 @@ bool InitialStateIterator::on_number(number::Number *number) {
return this->client_->send_number_state(number, number->state); return this->client_->send_number_state(number, number->state);
} }
#endif #endif
#ifdef USE_DATETIME_DATE
bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_state(date); }
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); } bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); }
#endif #endif

View File

@ -43,6 +43,9 @@ class InitialStateIterator : public ComponentIterator {
#ifdef USE_NUMBER #ifdef USE_NUMBER
bool on_number(number::Number *number) override; bool on_number(number::Number *number) override;
#endif #endif
#ifdef USE_DATETIME_DATE
bool on_date(datetime::DateEntity *date) override;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool on_text(text::Text *text) override; bool on_text(text::Text *text) override;
#endif #endif

View File

@ -22,7 +22,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
if CORE.is_esp32 or CORE.is_libretiny: if CORE.is_esp32 or CORE.is_libretiny:
# https://github.com/esphome/AsyncTCP/blob/master/library.json # 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: elif CORE.is_esp8266:
# https://github.com/esphome/ESPAsyncTCP # https://github.com/esphome/ESPAsyncTCP
cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0")

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,62 @@
#pragma once
#include <utility>
#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

View File

@ -0,0 +1,71 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include "at581x.h"
namespace esphome {
namespace at581x {
template<typename... Ts> class AT581XResetAction : public Action<Ts...>, public Parented<AT581XComponent> {
public:
void play(Ts... x) { this->parent_->reset_hardware_frontend(); }
};
template<typename... Ts> class AT581XSettingsAction : public Action<Ts...>, public Parented<AT581XComponent> {
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

View File

@ -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))

View File

@ -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

View File

@ -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<AT581XComponent> {
protected:
void write_state(bool state) override;
};
} // namespace at581x
} // namespace esphome

View File

@ -117,7 +117,7 @@ void ATM90E26Component::setup() {
this->write16_(ATM90E26_REGISTER_ADJSTART, this->write16_(ATM90E26_REGISTER_ADJSTART,
0x8765); // Checks correctness of 31-3A registers and starts normal measurement if ok 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 if (sys_status & 0xC000) { // Checksum 1 Error
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS1 was incorrect, expected: 0x%04X", 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_() { 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; return current / 1000.0f;
} }
float ATM90E26Component::get_line_voltage_() { 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; return voltage / 100.0f;
} }
float ATM90E26Component::get_active_power_() { 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; return (float) val;
} }
float ATM90E26Component::get_reactive_power_() { 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; return (float) val;
} }
float ATM90E26Component::get_power_factor_() { 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) { if (val & 0x8000) {
return -(val & 0x7FF) / 1000.0f; return -(val & 0x7FF) / 1000.0f;
} else { } else {
@ -206,7 +206,7 @@ float ATM90E26Component::get_power_factor_() {
} }
float ATM90E26Component::get_forward_active_energy_() { 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) { if ((UINT32_MAX - this->cumulative_forward_active_energy_) > val) {
this->cumulative_forward_active_energy_ += val; this->cumulative_forward_active_energy_ += val;
} else { } else {
@ -217,7 +217,7 @@ float ATM90E26Component::get_forward_active_energy_() {
} }
float ATM90E26Component::get_reverse_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) { if (UINT32_MAX - this->cumulative_reverse_active_energy_ > val) {
this->cumulative_reverse_active_energy_ += val; this->cumulative_reverse_active_energy_ += val;
} else { } else {
@ -227,7 +227,7 @@ float ATM90E26Component::get_reverse_active_energy_() {
} }
float ATM90E26Component::get_frequency_() { 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; return freq / 100.0f;
} }

View File

@ -7,82 +7,128 @@ namespace esphome {
namespace atm90e32 { namespace atm90e32 {
static const char *const TAG = "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() { void ATM90E32Component::update() {
if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) { if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) {
this->status_set_warning(); this->status_set_warning();
return; return;
} }
this->set_publish_interval_flag_(true);
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->status_clear_warning(); this->status_clear_warning();
} }
@ -101,29 +147,51 @@ void ATM90E32Component::setup() {
} }
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset 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_CFGREGACCEN, 0x55AA); // enable register config access
this->write16_(ATM90E32_REGISTER_METEREN, 0x0001); // Enable Metering if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != 0x55AA) {
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != 0x0001) {
ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings"); ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
this->mark_failed(); this->mark_failed();
return; 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_METEREN, 0x0001); // Enable Metering
this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654); // ZX2, ZX1, ZX0 pin config this->write16_(ATM90E32_REGISTER_SAGPEAKDETCFG, 0xFF3F); // Peak Detector time ms (15:8), Sag Period ms (7:0)
this->write16_(ATM90E32_REGISTER_MMODE0, mmode0); // Mode Config (frequency set in main program) this->write16_(ATM90E32_REGISTER_PLCONSTH, 0x0861); // PL Constant MSB (default) = 140625000
this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_); // PGA Gain Configuration for Current Channels this->write16_(ATM90E32_REGISTER_PLCONSTL, 0xC468); // PL Constant LSB (default)
this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x1D4C); // All Active Startup Power Threshold - 0.02A/0.00032 = 7500 this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654); // ZX2, ZX1, ZX0 pin config
this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50% this->write16_(ATM90E32_REGISTER_MMODE0, mmode0); // Mode Config (frequency set in main program)
this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750 this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_); // PGA Gain Configuration for Current Channels
this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10% this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x1D4C); // All Active Startup Power Threshold - 0.02A/0.00032 = 7500
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[0].volt_gain_); // A Voltage rms gain this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[0].ct_gain_); // A line current gain this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[1].volt_gain_); // B Voltage rms gain this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[1].ct_gain_); // B line current gain this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10%
this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[2].volt_gain_); // C Voltage rms gain // Setup voltage and current calibration offsets for PHASE A
this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[2].ct_gain_); // C line current gain this->phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA);
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration 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() { void ATM90E32Component::dump_config() {
@ -133,43 +201,54 @@ void ATM90E32Component::dump_config() {
ESP_LOGE(TAG, "Communication with ATM90E32 failed!"); ESP_LOGE(TAG, "Communication with ATM90E32 failed!");
} }
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage A", this->phase_[0].voltage_sensor_); LOG_SENSOR(" ", "Voltage A", this->phase_[PHASEA].voltage_sensor_);
LOG_SENSOR(" ", "Current A", this->phase_[0].current_sensor_); LOG_SENSOR(" ", "Current A", this->phase_[PHASEA].current_sensor_);
LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_); LOG_SENSOR(" ", "Power A", this->phase_[PHASEA].power_sensor_);
LOG_SENSOR(" ", "Reactive Power A", this->phase_[0].reactive_power_sensor_); LOG_SENSOR(" ", "Reactive Power A", this->phase_[PHASEA].reactive_power_sensor_);
LOG_SENSOR(" ", "PF A", this->phase_[0].power_factor_sensor_); LOG_SENSOR(" ", "PF A", this->phase_[PHASEA].power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[0].forward_active_energy_sensor_); LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[PHASEA].forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[0].reverse_active_energy_sensor_); LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[PHASEA].reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_); LOG_SENSOR(" ", "Harmonic Power A", this->phase_[PHASEA].harmonic_active_power_sensor_);
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_); LOG_SENSOR(" ", "Phase Angle A", this->phase_[PHASEA].phase_angle_sensor_);
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_); LOG_SENSOR(" ", "Peak Current A", this->phase_[PHASEA].peak_current_sensor_);
LOG_SENSOR(" ", "Reactive Power B", this->phase_[1].reactive_power_sensor_); LOG_SENSOR(" ", "Voltage B", this->phase_[PHASEB].voltage_sensor_);
LOG_SENSOR(" ", "PF B", this->phase_[1].power_factor_sensor_); LOG_SENSOR(" ", "Current B", this->phase_[PHASEB].current_sensor_);
LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[1].forward_active_energy_sensor_); LOG_SENSOR(" ", "Power B", this->phase_[PHASEB].power_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[1].reverse_active_energy_sensor_); LOG_SENSOR(" ", "Reactive Power B", this->phase_[PHASEB].reactive_power_sensor_);
LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_); LOG_SENSOR(" ", "PF B", this->phase_[PHASEB].power_factor_sensor_);
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_); LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[PHASEB].forward_active_energy_sensor_);
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_); LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[PHASEB].reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Reactive Power C", this->phase_[2].reactive_power_sensor_); LOG_SENSOR(" ", "Harmonic Power A", this->phase_[PHASEB].harmonic_active_power_sensor_);
LOG_SENSOR(" ", "PF C", this->phase_[2].power_factor_sensor_); LOG_SENSOR(" ", "Phase Angle A", this->phase_[PHASEB].phase_angle_sensor_);
LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[2].forward_active_energy_sensor_); LOG_SENSOR(" ", "Peak Current A", this->phase_[PHASEB].peak_current_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[2].reverse_active_energy_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(" ", "Frequency", this->freq_sensor_);
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_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) { uint16_t ATM90E32Component::read16_(uint16_t a_register) {
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03); uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
uint8_t addrl = (a_register & 0xFF); uint8_t addrl = (a_register & 0xFF);
uint8_t data[2]; uint8_t data[2];
uint16_t output; uint16_t output;
this->enable(); this->enable();
delayMicroseconds(10); delay_microseconds_safe(10);
this->write_byte(addrh); this->write_byte(addrh);
this->write_byte(addrl); this->write_byte(addrl);
delayMicroseconds(4);
this->read_array(data, 2); this->read_array(data, 2);
this->disable(); 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) { int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
uint16_t val_h = this->read16_(addr_h); const uint16_t val_h = this->read16_(addr_h);
uint16_t val_l = this->read16_(addr_l); const uint16_t val_l = this->read16_(addr_l);
int32_t val = (val_h << 16) | val_l; const int32_t val = (val_h << 16) | val_l;
ESP_LOGVV(TAG, ESP_LOGVV(TAG,
"read32_ addr_h 0x%04" PRIX16 " val_h 0x%04" PRIX16 " addr_l 0x%04" PRIX16 " val_l 0x%04" PRIX16 "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) { 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); ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val);
this->enable(); this->enable();
delayMicroseconds(10); this->write_byte16(a_register);
this->write_byte(addrh); this->write_byte16(val);
this->write_byte(addrl);
delayMicroseconds(4);
this->write_byte((val >> 8) & 0xff);
this->write_byte(val & 0xFF);
this->disable(); 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_() { float ATM90E32Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; }
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSA);
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; return (float) voltage / 100;
} }
float ATM90E32Component::get_line_voltage_b_() {
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSB); float ATM90E32Component::get_phase_voltage_avg_(uint8_t phase) {
return (float) voltage / 100; 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); float ATM90E32Component::get_phase_current_avg_(uint8_t phase) {
return (float) voltage / 100; 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; return (float) current / 1000;
} }
float ATM90E32Component::get_line_current_b_() {
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSB); float ATM90E32Component::get_phase_active_power_(uint8_t phase) {
return (float) current / 1000; const int val = this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
}
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);
return val * 0.00032f; 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; 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; return val * 0.00032f;
} }
float ATM90E32Component::get_reactive_power_a_() {
int val = this->read32_(ATM90E32_REGISTER_QMEANA, ATM90E32_REGISTER_QMEANALSB); float ATM90E32Component::get_phase_angle_(uint8_t phase) {
return val * 0.00032f; 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); float ATM90E32Component::get_phase_peak_current_(uint8_t phase) {
return val * 0.00032f; int16_t val = (float) this->read16_(ATM90E32_REGISTER_IPEAK + phase);
} if (!this->peak_current_signed_)
float ATM90E32Component::get_reactive_power_c_() { val = abs(val);
int val = this->read32_(ATM90E32_REGISTER_QMEANC, ATM90E32_REGISTER_QMEANCLSB); // phase register * phase current gain value / 1000 * 2^13
return val * 0.00032f; return (float) (val * this->phase_[phase].ct_gain_ / 8192000.0);
}
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_frequency_() { 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; return (float) freq / 100;
} }
float ATM90E32Component::get_chip_temperature_() { 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; 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 atm90e32
} // namespace esphome } // namespace esphome

View File

@ -3,14 +3,19 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.h" #include "esphome/components/spi/spi.h"
#include "atm90e32_reg.h"
namespace esphome { namespace esphome {
namespace atm90e32 { namespace atm90e32 {
class ATM90E32Component : public PollingComponent, class ATM90E32Component : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> { spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_1MHZ> {
public: 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 setup() override;
void dump_config() override; void dump_config() override;
float get_setup_priority() const 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_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_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_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) { void set_forward_active_energy_sensor(int phase, sensor::Sensor *obj) {
this->phase_[phase].forward_active_energy_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; 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_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_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_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) { void set_chip_temperature_sensor(sensor::Sensor *chip_temperature_sensor) {
chip_temperature_sensor_ = chip_temperature_sensor; chip_temperature_sensor_ = chip_temperature_sensor;
} }
void set_line_freq(int freq) { line_freq_ = freq; } void set_line_freq(int freq) { line_freq_ = freq; }
void set_current_phases(int phases) { current_phases_ = phases; } void set_current_phases(int phases) { current_phases_ = phases; }
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; } 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: protected:
uint16_t read16_(uint16_t a_register); uint16_t read16_(uint16_t a_register);
int read32_(uint16_t addr_h, uint16_t addr_l); int read32_(uint16_t addr_h, uint16_t addr_l);
void write16_(uint16_t a_register, uint16_t val); void write16_(uint16_t a_register, uint16_t val);
float get_local_phase_voltage_(uint8_t /*phase*/);
float get_line_voltage_a_(); float get_local_phase_current_(uint8_t /*phase*/);
float get_line_voltage_b_(); float get_local_phase_active_power_(uint8_t /*phase*/);
float get_line_voltage_c_(); float get_local_phase_reactive_power_(uint8_t /*phase*/);
float get_line_current_a_(); float get_local_phase_power_factor_(uint8_t /*phase*/);
float get_line_current_b_(); float get_local_phase_forward_active_energy_(uint8_t /*phase*/);
float get_line_current_c_(); float get_local_phase_reverse_active_energy_(uint8_t /*phase*/);
float get_active_power_a_(); float get_local_phase_angle_(uint8_t /*phase*/);
float get_active_power_b_(); float get_local_phase_harmonic_active_power_(uint8_t /*phase*/);
float get_active_power_c_(); float get_local_phase_peak_current_(uint8_t /*phase*/);
float get_reactive_power_a_(); float get_phase_voltage_(uint8_t /*phase*/);
float get_reactive_power_b_(); float get_phase_voltage_avg_(uint8_t /*phase*/);
float get_reactive_power_c_(); float get_phase_current_(uint8_t /*phase*/);
float get_power_factor_a_(); float get_phase_current_avg_(uint8_t /*phase*/);
float get_power_factor_b_(); float get_phase_active_power_(uint8_t /*phase*/);
float get_power_factor_c_(); float get_phase_reactive_power_(uint8_t /*phase*/);
float get_forward_active_energy_a_(); float get_phase_power_factor_(uint8_t /*phase*/);
float get_forward_active_energy_b_(); float get_phase_forward_active_energy_(uint8_t /*phase*/);
float get_forward_active_energy_c_(); float get_phase_reverse_active_energy_(uint8_t /*phase*/);
float get_reverse_active_energy_a_(); float get_phase_angle_(uint8_t /*phase*/);
float get_reverse_active_energy_b_(); float get_phase_harmonic_active_power_(uint8_t /*phase*/);
float get_reverse_active_energy_c_(); float get_phase_peak_current_(uint8_t /*phase*/);
float get_frequency_(); float get_frequency_();
float get_chip_temperature_(); 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 { struct ATM90E32Phase {
uint16_t volt_gain_{7305}; uint16_t voltage_gain_{7305};
uint16_t ct_gain_{27961}; 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 *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *reactive_power_sensor_{nullptr}; sensor::Sensor *reactive_power_sensor_{nullptr};
sensor::Sensor *apparent_power_sensor_{nullptr};
sensor::Sensor *power_factor_sensor_{nullptr}; sensor::Sensor *power_factor_sensor_{nullptr};
sensor::Sensor *forward_active_energy_sensor_{nullptr}; sensor::Sensor *forward_active_energy_sensor_{nullptr};
sensor::Sensor *reverse_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_forward_active_energy_{0};
uint32_t cumulative_reverse_active_energy_{0}; uint32_t cumulative_reverse_active_energy_{0};
} phase_[3]; } phase_[3];
sensor::Sensor *freq_sensor_{nullptr}; sensor::Sensor *freq_sensor_{nullptr};
sensor::Sensor *chip_temperature_sensor_{nullptr}; sensor::Sensor *chip_temperature_sensor_{nullptr};
uint16_t pga_gain_{0x15}; uint16_t pga_gain_{0x15};
int line_freq_{60}; int line_freq_{60};
int current_phases_{3}; int current_phases_{3};
bool publish_interval_flag_{true};
bool peak_current_signed_{false};
}; };
} // namespace atm90e32 } // namespace atm90e32

View File

@ -131,10 +131,12 @@ static const uint16_t ATM90E32_REGISTER_IOFFSETN = 0x6E; // N Current Offset
/* ENERGY REGISTERS */ /* ENERGY REGISTERS */
static const uint16_t ATM90E32_REGISTER_APENERGYT = 0x80; // Total Forward Active 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_APENERGYA = 0x81; // A Forward Active
static const uint16_t ATM90E32_REGISTER_APENERGYB = 0x82; // B 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_APENERGYC = 0x83; // C Forward Active
static const uint16_t ATM90E32_REGISTER_ANENERGYT = 0x84; // Total Reverse 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_ANENERGYA = 0x85; // A Reverse Active
static const uint16_t ATM90E32_REGISTER_ANENERGYB = 0x86; // B Reverse Active static const uint16_t ATM90E32_REGISTER_ANENERGYB = 0x86; // B Reverse Active
static const uint16_t ATM90E32_REGISTER_ANENERGYC = 0x87; // C 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 */ /* POWER & P.F. REGISTERS */
static const uint16_t ATM90E32_REGISTER_PMEANT = 0xB0; // Total Mean Power (P) 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_PMEANA = 0xB1; // A Mean Power (P)
static const uint16_t ATM90E32_REGISTER_PMEANB = 0xB2; // B 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_PMEANC = 0xB3; // C Mean Power (P)
static const uint16_t ATM90E32_REGISTER_QMEANT = 0xB4; // Total Mean Power (Q) 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_QMEANA = 0xB5; // A Mean Power (Q)
static const uint16_t ATM90E32_REGISTER_QMEANB = 0xB6; // B Mean Power (Q) static const uint16_t ATM90E32_REGISTER_QMEANB = 0xB6; // B Mean Power (Q)
static const uint16_t ATM90E32_REGISTER_QMEANC = 0xB7; // C Mean Power (Q) static const uint16_t ATM90E32_REGISTER_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_SMEANB = 0xBA; // B Mean Power (S)
static const uint16_t ATM90E32_REGISTER_SMEANC = 0xBB; // C 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_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_PFMEANA = 0xBD; // A Power Factor
static const uint16_t ATM90E32_REGISTER_PFMEANB = 0xBE; // B 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_PFMEANC = 0xBF; // C Power Factor
static const uint16_t ATM90E32_REGISTER_PMEANTLSB = 0xC0; // Lower Word (Tot. Act. Power) 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_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_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_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_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_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_QMEANBLSB = 0xC6; // Lower Word (B React. Power)
static const uint16_t ATM90E32_REGISTER_QMEANCLSB = 0xC7; // Lower Word (C React. Power) static const uint16_t ATM90E32_REGISTER_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_PMEANBF = 0xD2; // B Active Fund. Power
static const uint16_t ATM90E32_REGISTER_PMEANCF = 0xD3; // C 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_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_PMEANAH = 0xD5; // A Active Harm. Power
static const uint16_t ATM90E32_REGISTER_PMEANBH = 0xD6; // B 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_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_URMSA = 0xD9; // A RMS Voltage
static const uint16_t ATM90E32_REGISTER_URMSB = 0xDA; // B 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_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_IRMSA = 0xDD; // A RMS Current
static const uint16_t ATM90E32_REGISTER_IRMSB = 0xDE; // B RMS Current static const uint16_t ATM90E32_REGISTER_IRMSB = 0xDE; // B RMS Current
static const uint16_t ATM90E32_REGISTER_IRMSC = 0xDF; // C 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_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_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_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_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_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_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_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_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_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_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_IRMSBLSB = 0xEE; // Lower Word (B RMS Current)
static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C 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_UPEAKA = 0xF1; // A Voltage Peak
static const uint16_t ATM90E32_REGISTER_UPEAKB = 0xF2; // B 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_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_IPEAKA = 0xF5; // A Current Peak
static const uint16_t ATM90E32_REGISTER_IPEAKB = 0xF6; // B 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_IPEAKC = 0xF7; // C Current Peak
static const uint16_t ATM90E32_REGISTER_FREQ = 0xF8; // Frequency 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_PANGLEA = 0xF9; // A Mean Phase Angle
static const uint16_t ATM90E32_REGISTER_PANGLEB = 0xFA; // B 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 static const uint16_t ATM90E32_REGISTER_PANGLEC = 0xFB; // C Mean Phase Angle

View File

@ -9,8 +9,10 @@ from esphome.const import (
CONF_PHASE_A, CONF_PHASE_A,
CONF_PHASE_B, CONF_PHASE_B,
CONF_PHASE_C, CONF_PHASE_C,
CONF_PHASE_ANGLE,
CONF_POWER, CONF_POWER,
CONF_POWER_FACTOR, CONF_POWER_FACTOR,
CONF_APPARENT_POWER,
CONF_FREQUENCY, CONF_FREQUENCY,
CONF_FORWARD_ACTIVE_ENERGY, CONF_FORWARD_ACTIVE_ENERGY,
CONF_REVERSE_ACTIVE_ENERGY, CONF_REVERSE_ACTIVE_ENERGY,
@ -25,12 +27,13 @@ from esphome.const import (
ICON_CURRENT_AC, ICON_CURRENT_AC,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING, STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_DEGREES,
UNIT_CELSIUS,
UNIT_HERTZ, UNIT_HERTZ,
UNIT_VOLT, UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_CELSIUS,
UNIT_VOLT_AMPS_REACTIVE, UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT,
UNIT_WATT_HOURS, UNIT_WATT_HOURS,
) )
@ -40,6 +43,10 @@ CONF_GAIN_PGA = "gain_pga"
CONF_CURRENT_PHASES = "current_phases" CONF_CURRENT_PHASES = "current_phases"
CONF_GAIN_VOLTAGE = "gain_voltage" CONF_GAIN_VOLTAGE = "gain_voltage"
CONF_GAIN_CT = "gain_ct" 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 = { LINE_FREQS = {
"50HZ": 50, "50HZ": 50,
"60HZ": 60, "60HZ": 60,
@ -85,6 +92,12 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
accuracy_decimals=2, accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT, 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( cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
accuracy_decimals=2, accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER_FACTOR, device_class=DEVICE_CLASS_POWER_FACTOR,
@ -102,6 +115,24 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_ENERGY, device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING, 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_VOLTAGE, default=7305): cv.uint16_t,
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
} }
@ -132,6 +163,7 @@ CONFIG_SCHEMA = (
CURRENT_PHASES, upper=True CURRENT_PHASES, upper=True
), ),
cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True), cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True),
cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean,
} }
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
@ -162,6 +194,9 @@ async def to_code(config):
if reactive_power_config := conf.get(CONF_REACTIVE_POWER): if reactive_power_config := conf.get(CONF_REACTIVE_POWER):
sens = await sensor.new_sensor(reactive_power_config) sens = await sensor.new_sensor(reactive_power_config)
cg.add(var.set_reactive_power_sensor(i, sens)) 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): if power_factor_config := conf.get(CONF_POWER_FACTOR):
sens = await sensor.new_sensor(power_factor_config) sens = await sensor.new_sensor(power_factor_config)
cg.add(var.set_power_factor_sensor(i, sens)) 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): if reverse_active_energy_config := conf.get(CONF_REVERSE_ACTIVE_ENERGY):
sens = await sensor.new_sensor(reverse_active_energy_config) sens = await sensor.new_sensor(reverse_active_energy_config)
cg.add(var.set_reverse_active_energy_sensor(i, sens)) 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): if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(frequency_config) 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_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES])) cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA])) cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED]))

View File

@ -194,8 +194,8 @@ void BangBangClimate::dump_config() {
ESP_LOGCONFIG(TAG, " Supports HEAT: %s", YESNO(this->supports_heat_)); ESP_LOGCONFIG(TAG, " Supports HEAT: %s", YESNO(this->supports_heat_));
ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_)); ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_));
ESP_LOGCONFIG(TAG, " Supports AWAY mode: %s", YESNO(this->supports_away_)); 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 Low: %.2f°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 High: %.2f°C", this->normal_config_.default_temperature_high);
} }
BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig() = default; BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig() = default;

View File

@ -8,8 +8,11 @@ from esphome.const import (
CONF_IBEACON_MINOR, CONF_IBEACON_MINOR,
CONF_IBEACON_UUID, CONF_IBEACON_UUID,
CONF_MIN_RSSI, CONF_MIN_RSSI,
CONF_TIMEOUT,
) )
CONF_IRK = "irk"
DEPENDENCIES = ["esp32_ble_tracker"] DEPENDENCIES = ["esp32_ble_tracker"]
ble_presence_ns = cg.esphome_ns.namespace("ble_presence") ble_presence_ns = cg.esphome_ns.namespace("ble_presence")
@ -34,10 +37,12 @@ CONFIG_SCHEMA = cv.All(
.extend( .extend(
{ {
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, 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_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t,
cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t,
cv.Optional(CONF_IBEACON_UUID): cv.uuid, 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.Optional(CONF_MIN_RSSI): cv.All(
cv.decibel, cv.int_range(min=-100, max=-30) 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(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
.extend(cv.COMPONENT_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, _validate,
) )
@ -55,12 +62,17 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(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): if min_rssi := config.get(CONF_MIN_RSSI):
cg.add(var.set_minimum_rssi(min_rssi)) cg.add(var.set_minimum_rssi(min_rssi))
if mac_address := config.get(CONF_MAC_ADDRESS): if mac_address := config.get(CONF_MAC_ADDRESS):
cg.add(var.set_address(mac_address.as_hex)) 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 service_uuid := config.get(CONF_SERVICE_UUID):
if len(service_uuid) == len(esp32_ble_tracker.bt_uuid16_format): if len(service_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(service_uuid))) cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(service_uuid)))

View File

@ -6,6 +6,16 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#ifdef USE_ARDUINO
#include "mbedtls/aes.h"
#include "mbedtls/base64.h"
#endif
#ifdef USE_ESP_IDF
#define MBEDTLS_AES_ALT
#include <aes_alt.h>
#endif
namespace esphome { namespace esphome {
namespace ble_presence { namespace ble_presence {
@ -17,6 +27,10 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
this->match_by_ = MATCH_BY_MAC_ADDRESS; this->match_by_ = MATCH_BY_MAC_ADDRESS;
this->address_ = 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) { void set_service_uuid16(uint16_t uuid) {
this->match_by_ = MATCH_BY_SERVICE_UUID; this->match_by_ = MATCH_BY_SERVICE_UUID;
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(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->check_minimum_rssi_ = true;
this->minimum_rssi_ = rssi; this->minimum_rssi_ = rssi;
} }
void on_scan_end() override { void set_timeout(uint32_t timeout) { this->timeout_ = timeout; }
if (!this->found_)
this->publish_state(false);
this->found_ = false;
}
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
if (this->check_minimum_rssi_ && this->minimum_rssi_ > device.get_rssi()) { if (this->check_minimum_rssi_ && this->minimum_rssi_ > device.get_rssi()) {
return false; return false;
@ -57,6 +67,12 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
switch (this->match_by_) { switch (this->match_by_) {
case MATCH_BY_MAC_ADDRESS: case MATCH_BY_MAC_ADDRESS:
if (device.address_uint64() == this->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->publish_state(true);
this->found_ = true; this->found_ = true;
return true; return true;
@ -65,8 +81,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
case MATCH_BY_SERVICE_UUID: case MATCH_BY_SERVICE_UUID:
for (auto uuid : device.get_service_uuids()) { for (auto uuid : device.get_service_uuids()) {
if (this->uuid_ == uuid) { if (this->uuid_ == uuid) {
this->publish_state(true); this->set_found_(true);
this->found_ = true;
return true; return true;
} }
} }
@ -90,20 +105,31 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
return false; return false;
} }
this->publish_state(true); this->set_found_(true);
this->found_ = true;
return true; return true;
} }
return false; return false;
} }
void loop() override {
if (this->found_ && this->last_seen_ + this->timeout_ < millis())
this->set_found_(false);
}
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; } float get_setup_priority() const override { return setup_priority::DATA; }
protected: 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_; MatchType match_by_;
uint64_t address_; uint64_t address_;
uint8_t *irk_;
esp32_ble_tracker::ESPBTUUID uuid_; esp32_ble_tracker::ESPBTUUID uuid_;
@ -117,7 +143,46 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
bool check_ibeacon_minor_{false}; bool check_ibeacon_minor_{false};
bool check_minimum_rssi_{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}; bool found_{false};
uint32_t last_seen_{};
uint32_t timeout_{};
}; };
} // namespace ble_presence } // namespace ble_presence

View File

@ -118,7 +118,7 @@ void CSE7766Component::parse_data_() {
uint32_t power_coeff = 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); uint32_t power_cycle = this->get_24_bit_uint_(17);
uint8_t adj = this->raw_data_[20]; uint8_t adj = this->raw_data_[20];
uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22]; uint16_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
bool have_power = adj & 0x10; bool have_power = adj & 0x10;
bool have_current = adj & 0x20; bool have_current = adj & 0x20;
@ -132,8 +132,19 @@ void CSE7766Component::parse_data_() {
} }
} }
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;
}
uint16_t cf_diff = cf_pulses - this->cf_pulses_last_;
this->cf_pulses_total_ += cf_diff;
this->cf_pulses_last_ = cf_pulses;
energy = this->cf_pulses_total_ * float(power_coeff) / 1000000.0f / 3600.0f;
this->energy_sensor_->publish_state(energy);
}
float power = 0.0f; float power = 0.0f;
float energy = 0.0f;
if (power_cycle_exceeds_range) { if (power_cycle_exceeds_range) {
// Datasheet: power cycle exceeding range means active power is 0 // Datasheet: power cycle exceeding range means active power is 0
if (this->power_sensor_ != nullptr) { if (this->power_sensor_ != nullptr) {
@ -144,27 +155,6 @@ void CSE7766Component::parse_data_() {
if (this->power_sensor_ != nullptr) { if (this->power_sensor_ != nullptr) {
this->power_sensor_->publish_state(power); this->power_sensor_->publish_state(power);
} }
// Add CF pulses to the total energy only if we have Power coefficient to multiply by
if (this->cf_pulses_last_ == 0) {
this->cf_pulses_last_ = cf_pulses;
}
uint32_t cf_diff;
if (cf_pulses < this->cf_pulses_last_) {
cf_diff = cf_pulses + (0x10000 - this->cf_pulses_last_);
} else {
cf_diff = cf_pulses - this->cf_pulses_last_;
}
this->cf_pulses_last_ = cf_pulses;
energy = cf_diff * float(power_coeff) / 1000000.0f / 3600.0f;
this->energy_total_ += energy;
if (this->energy_sensor_ != nullptr)
this->energy_sensor_->publish_state(this->energy_total_);
} else if ((this->energy_sensor_ != nullptr) && !this->energy_sensor_->has_state()) {
this->energy_sensor_->publish_state(0);
} }
float current = 0.0f; float current = 0.0f;
@ -183,6 +173,32 @@ void CSE7766Component::parse_data_() {
} }
} }
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);
}
this->power_factor_sensor_->publish_state(pf);
}
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
{ {
std::stringstream ss; std::stringstream ss;
@ -215,6 +231,8 @@ void CSE7766Component::dump_config() {
LOG_SENSOR(" ", "Current", this->current_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Power", this->power_sensor_); LOG_SENSOR(" ", "Power", this->power_sensor_);
LOG_SENSOR(" ", "Energy", this->energy_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); this->check_uart_settings(4800);
} }

View File

@ -13,6 +13,10 @@ class CSE7766Component : public Component, public uart::UARTDevice {
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_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_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_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; void loop() override;
float get_setup_priority() const override; float get_setup_priority() const override;
@ -30,8 +34,10 @@ class CSE7766Component : public Component, public uart::UARTDevice {
sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *energy_sensor_{nullptr}; sensor::Sensor *energy_sensor_{nullptr};
float energy_total_{0.0f}; sensor::Sensor *apparent_power_sensor_{nullptr};
uint32_t cf_pulses_last_{0}; sensor::Sensor *power_factor_sensor_{nullptr};
uint32_t cf_pulses_total_{0};
uint16_t cf_pulses_last_{0};
}; };
} // namespace cse7766 } // namespace cse7766

View File

@ -2,19 +2,24 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor, uart from esphome.components import sensor, uart
from esphome.const import ( from esphome.const import (
CONF_APPARENT_POWER,
CONF_CURRENT, CONF_CURRENT,
CONF_ENERGY, CONF_ENERGY,
CONF_ID, CONF_ID,
CONF_POWER, CONF_POWER,
CONF_POWER_FACTOR,
CONF_VOLTAGE, CONF_VOLTAGE,
DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING, STATE_CLASS_TOTAL_INCREASING,
UNIT_VOLT,
UNIT_AMPERE, UNIT_AMPERE,
UNIT_VOLT,
UNIT_VOLT_AMPS,
UNIT_WATT, UNIT_WATT,
UNIT_WATT_HOURS, UNIT_WATT_HOURS,
) )
@ -51,6 +56,17 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_ENERGY, device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING, 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) ).extend(uart.UART_DEVICE_SCHEMA)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
@ -75,3 +91,9 @@ async def to_code(config):
if energy_config := config.get(CONF_ENERGY): if energy_config := config.get(CONF_ENERGY):
sens = await sensor.new_sensor(energy_config) sens = await sensor.new_sensor(energy_config)
cg.add(var.set_energy_sensor(sens)) 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))

View File

@ -0,0 +1,6 @@
import esphome.codegen as cg
CODEOWNERS = ["@clydebarrow"]
DEPENDENCIES = ["i2c"]
cst226_ns = cg.esphome_ns.namespace("cst226")

View File

@ -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)))

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,6 @@
import esphome.codegen as cg
CODEOWNERS = ["@clydebarrow"]
DEPENDENCIES = ["i2c"]
cst816_ns = cg.esphome_ns.namespace("cst816")

View File

@ -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])

View File

@ -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<CST816Touchscreen> {
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

View File

@ -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)))

View File

@ -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

View File

@ -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<CST816ButtonListener *> button_listeners_;
bool button_touched_{};
};
} // namespace cst816
} // namespace esphome

View File

@ -2,8 +2,10 @@ import base64
import secrets import secrets
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
import re
import requests import requests
from ruamel.yaml import YAML
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
@ -11,7 +13,6 @@ import esphome.final_validate as fv
from esphome import git from esphome import git
from esphome.components.packages import validate_source_shorthand from esphome.components.packages import validate_source_shorthand
from esphome.const import CONF_REF, CONF_WIFI, CONF_ESPHOME, CONF_PROJECT from esphome.const import CONF_REF, CONF_WIFI, CONF_ESPHOME, CONF_PROJECT
from esphome.wizard import wizard_file
from esphome.yaml_util import dump from esphome.yaml_util import dump
dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import") dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import")
@ -94,75 +95,74 @@ def import_config(
if p.exists(): if p.exists():
raise FileExistsError raise FileExistsError
if project_name == "esphome.web": git_file = git.GitFile.from_shorthand(import_url)
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"
kwargs = { if git_file.query and "full_config" in git_file.query:
"name": name, url = git_file.raw_url
"friendly_name": friendly_name, try:
"platform": platform, req = requests.get(url, timeout=30)
"board": board, req.raise_for_status()
"ssid": "!secret wifi_ssid", except requests.exceptions.RequestException as e:
"psk": "!secret wifi_password", 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: if encryption:
noise_psk = secrets.token_bytes(32) noise_psk = secrets.token_bytes(32)
key = base64.b64encode(noise_psk).decode() key = base64.b64encode(noise_psk).decode()
kwargs["api_encryption_key"] = key config["api"] = {"encryption": {"key": key}}
p.write_text( output = dump(config)
wizard_file(**kwargs),
encoding="utf8",
)
else:
git_file = git.GitFile.from_shorthand(import_url)
if git_file.query and "full_config" in git_file.query: if network == CONF_WIFI:
url = git_file.raw_url output += WIFI_CONFIG
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
p.write_text(req.text, encoding="utf8") p.write_text(output, 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")

View File

@ -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

View File

@ -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

View File

@ -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<uint16_t> get_year() const { return this->year_; }
optional<uint8_t> get_month() const { return this->month_; }
optional<uint8_t> get_day() const { return this->day_; }
protected:
void validate_();
DateEntity *parent_;
optional<int16_t> year_;
optional<uint8_t> month_;
optional<uint8_t> day_;
};
template<typename... Ts> class DateSetAction : public Action<Ts...>, public Parented<DateEntity> {
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

View File

@ -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<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
protected:
CallbackManager<void()> state_callback_;
bool has_state_{false};
};
class DateTimeStateTrigger : public Trigger<ESPTime> {
public:
explicit DateTimeStateTrigger(DateTimeBase *parent) {
parent->add_on_state_callback([this, parent]() { this->trigger(parent->state_as_esptime()); });
}
};
} // namespace datetime
} // namespace esphome

View File

@ -6,6 +6,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/version.h" #include "esphome/core/version.h"
#include <cinttypes> #include <cinttypes>
#include <climits>
#ifdef USE_ESP32 #ifdef USE_ESP32
@ -49,6 +50,8 @@ static uint32_t get_free_heap() {
return rp2040.getFreeHeap(); return rp2040.getFreeHeap();
#elif defined(USE_LIBRETINY) #elif defined(USE_LIBRETINY)
return lt_heap_get_free(); return lt_heap_get_free();
#elif defined(USE_HOST)
return INT_MAX;
#endif #endif
} }

View File

@ -81,7 +81,7 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
#endif #endif
#if defined(USE_ESP32) #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; } 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(); App.run_safe_shutdown_hooks();
#if defined(USE_ESP32) #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()) if (this->sleep_duration_.has_value())
esp_sleep_enable_timer_wakeup(*this->sleep_duration_); esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
if (this->wakeup_pin_ != nullptr) { 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); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
} }
#endif #endif
#ifdef USE_ESP32_VARIANT_ESP32C3 #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6)
if (this->sleep_duration_.has_value()) if (this->sleep_duration_.has_value())
esp_sleep_enable_timer_wakeup(*this->sleep_duration_); esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
if (this->wakeup_pin_ != nullptr) { if (this->wakeup_pin_ != nullptr) {

View File

@ -1,7 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation 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 from esphome.components import uart
DEPENDENCIES = ["uart"] DEPENDENCIES = ["uart"]
@ -19,7 +19,6 @@ DFPlayerIsPlayingCondition = dfplayer_ns.class_(
MULTI_CONF = True MULTI_CONF = True
CONF_FOLDER = "folder" CONF_FOLDER = "folder"
CONF_LOOP = "loop" CONF_LOOP = "loop"
CONF_VOLUME = "volume"
CONF_EQ_PRESET = "eq_preset" CONF_EQ_PRESET = "eq_preset"
CONF_ON_FINISHED_PLAYBACK = "on_finished_playback" CONF_ON_FINISHED_PLAYBACK = "on_finished_playback"

View File

@ -7,10 +7,10 @@ namespace dfplayer {
static const char *const TAG = "dfplayer"; static const char *const TAG = "dfplayer";
void DFPlayer::play_folder(uint16_t folder, uint16_t file) { 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->ack_set_is_playing_ = true;
this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file); 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->ack_set_is_playing_ = true;
this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file); this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file);
} else { } else {

View File

@ -36,6 +36,21 @@ 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, 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) { 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 size_t line_stride = x_offset + w + x_pad; // length of each source line in pixels
@ -257,18 +272,81 @@ void Display::filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Co
this->filled_flat_side_triangle_(x3, y3, 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;
void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) { 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, &current_vertex_x, &current_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 x_start, y_start;
int width, height; int width, height;
this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &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]; char buffer[256];
int ret = vsnprintf(buffer, sizeof(buffer), format, arg); int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
if (ret > 0) 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) { void Display::image(int x, int y, BaseImage *image, Color color_on, Color color_off) {
@ -368,8 +446,8 @@ void Display::get_text_bounds(int x, int y, const char *text, BaseFont *font, Te
break; break;
} }
} }
void Display::print(int x, int y, BaseFont *font, Color color, const char *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); 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) { void Display::print(int x, int y, BaseFont *font, TextAlign align, const char *text) {
this->print(x, y, font, COLOR_ON, align, text); this->print(x, y, font, COLOR_ON, align, text);
@ -377,28 +455,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) { void Display::print(int x, int y, BaseFont *font, const char *text) {
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, 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, ...) { void Display::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) {
va_list arg; va_list arg;
va_start(arg, format); 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); va_end(arg);
} }
void Display::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) { void Display::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) {
va_list arg; va_list arg;
va_start(arg, format); 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); va_end(arg);
} }
void Display::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) { void Display::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) {
va_list arg; va_list arg;
va_start(arg, format); 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); va_end(arg);
} }
void Display::printf(int x, int y, BaseFont *font, const char *format, ...) { void Display::printf(int x, int y, BaseFont *font, const char *format, ...) {
va_list arg; va_list arg;
va_start(arg, format); 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); va_end(arg);
} }
void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; } void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; }

View File

@ -143,6 +143,42 @@ enum DisplayRotation {
DISPLAY_ROTATION_270_DEGREES = 270, 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 Display;
class DisplayPage; class DisplayPage;
class DisplayOnPageChangeTrigger; class DisplayOnPageChangeTrigger;
@ -170,7 +206,7 @@ class BaseImage {
class BaseFont { class BaseFont {
public: 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; virtual void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) = 0;
}; };
@ -181,10 +217,15 @@ class Display : public PollingComponent {
/// Clear the entire screen by filling it with OFF pixels. /// Clear the entire screen by filling it with OFF pixels.
void clear(); void clear();
/// Get the width of the image in pixels with rotation applied. /// Get the calculated width of the display in pixels with rotation applied.
virtual int get_width() = 0; virtual int get_width() { return this->get_width_internal(); }
/// Get the height of the image in pixels with rotation applied. /// Get the calculated height of the display in pixels with rotation applied.
virtual int get_height() = 0; 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. /// 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); } inline void draw_pixel_at(int x, int y) { this->draw_pixel_at(x, y, COLOR_ON); }
@ -223,6 +264,13 @@ class Display : public PollingComponent {
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color. /// 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); 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. /// 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); void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
@ -248,6 +296,42 @@ class Display : public PollingComponent {
/// Fill a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color. /// 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); 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`. /** Print `text` with the anchor point at [x,y] with `font`.
* *
* @param x The x coordinate of the text alignment anchor point. * @param x The x coordinate of the text alignment anchor point.
@ -256,8 +340,10 @@ class Display : public PollingComponent {
* @param color The color to draw the text with. * @param color The color to draw the text with.
* @param align The alignment of the text. * @param align The alignment of the text.
* @param text The text to draw. * @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`. /** Print `text` with the top left at [x,y] with `font`.
* *
@ -266,8 +352,9 @@ class Display : public PollingComponent {
* @param font The font to draw the text with. * @param font The font to draw the text with.
* @param color The color to draw the text with. * @param color The color to draw the text with.
* @param text The text to draw. * @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`. /** Print `text` with the anchor point at [x,y] with `font`.
* *
@ -288,6 +375,20 @@ class Display : public PollingComponent {
*/ */
void print(int x, int y, BaseFont *font, const char *text); 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`. /** 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 x The x coordinate of the text alignment anchor point.
@ -580,11 +681,15 @@ class Display : public PollingComponent {
protected: protected:
bool clamp_x_(int x, int w, int &min_x, int &max_x); 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); 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 do_update_();
void clear_clipping_(); 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 * This method fills a triangle using only integer variables by using a
* modified bresenham algorithm. * modified bresenham algorithm.

View File

@ -22,9 +22,6 @@ class DisplayBuffer : public Display {
/// Set a single pixel at the specified coordinates to the given color. /// Set a single pixel at the specified coordinates to the given color.
void draw_pixel_at(int x, int y, Color color) override; void draw_pixel_at(int x, int y, Color color) override;
virtual int get_height_internal() = 0;
virtual int get_width_internal() = 0;
protected: protected:
virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0; virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0;

View File

@ -34,24 +34,27 @@ void EKTF2232Touchscreen::setup() {
// Get touch resolution // Get touch resolution
uint8_t received[4]; uint8_t received[4];
this->write(GET_X_RES, 4); if (this->x_raw_max_ == this->x_raw_min_) {
if (this->read(received, 4)) { this->write(GET_X_RES, 4);
ESP_LOGE(TAG, "Failed to read X resolution!"); if (this->read(received, 4)) {
this->interrupt_pin_->detach_interrupt(); ESP_LOGE(TAG, "Failed to read X resolution!");
this->mark_failed(); this->interrupt_pin_->detach_interrupt();
return; 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->y_raw_max_ == this->y_raw_min_) {
if (this->read(received, 4)) { this->write(GET_Y_RES, 4);
ESP_LOGE(TAG, "Failed to read Y resolution!"); if (this->read(received, 4)) {
this->interrupt_pin_->detach_interrupt(); ESP_LOGE(TAG, "Failed to read Y resolution!");
this->mark_failed(); this->interrupt_pin_->detach_interrupt();
return; 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); this->set_power_state(true);
} }

View File

View File

@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate_ir
from esphome.const import CONF_ID
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)

View File

@ -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<float>(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<typename T> 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<typename T> 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<typename T> 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<typename T>
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

View File

@ -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<typename T> T reverse_(T val, size_t len);
template<typename T> void add_(T val, size_t len, esphome::remote_base::RemoteTransmitData *ata);
template<typename T> void add_(T val, esphome::remote_base::RemoteTransmitData *data);
template<typename T> void reverse_add_(T val, size_t len, esphome::remote_base::RemoteTransmitData *data);
uint8_t blades_ = EMMETI_BLADES_STOP;
};
} // namespace emmeti
} // namespace esphome

View File

@ -141,9 +141,13 @@ void ESP32ImprovComponent::loop() {
std::vector<std::string> urls = {ESPHOME_MY_LINK}; std::vector<std::string> urls = {ESPHOME_MY_LINK};
#ifdef USE_WEBSERVER #ifdef USE_WEBSERVER
auto ip = wifi::global_wifi_component->wifi_sta_ip(); for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); if (ip.is_ip4()) {
urls.push_back(webserver_url); std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
urls.push_back(webserver_url);
break;
}
}
#endif #endif
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls); std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
this->send_response_(data); this->send_response_(data);
@ -289,7 +293,7 @@ void ESP32ImprovComponent::process_incoming_data_() {
this->connecting_sta_ = sta; this->connecting_sta_ = sta;
wifi::global_wifi_component->set_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); this->set_state_(improv::STATE_PROVISIONING);
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
command.password.c_str()); command.password.c_str());

View File

@ -83,20 +83,22 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
# The default/recommended arduino framework version # The default/recommended arduino framework version
# - https://github.com/esp8266/Arduino/releases # - https://github.com/esp8266/Arduino/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266 # - 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 # The platformio/espressif8266 version to use for arduino 2 framework versions
# - https://github.com/platformio/platform-espressif8266/releases # - https://github.com/platformio/platform-espressif8266/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266 # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266
ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 3) ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 3)
# for arduino 3 framework versions # for arduino 3 framework versions
ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 2, 0) 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): def _arduino_check_versions(value):
value = value.copy() value = value.copy()
lookups = { lookups = {
"dev": (cv.Version(3, 0, 2), "https://github.com/esp8266/Arduino.git"), "dev": (cv.Version(3, 1, 2), "https://github.com/esp8266/Arduino.git"),
"latest": (cv.Version(3, 0, 2), None), "latest": (cv.Version(3, 1, 2), None),
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
} }
@ -116,7 +118,9 @@ def _arduino_check_versions(value):
platform_version = value.get(CONF_PLATFORM_VERSION) platform_version = value.get(CONF_PLATFORM_VERSION)
if platform_version is None: 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)) platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION))
elif version >= cv.Version(2, 5, 0): elif version >= cv.Version(2, 5, 0):
platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION)) platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION))

View File

@ -1,6 +1,13 @@
from esphome import pins from esphome import pins
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.final_validate as fv
import esphome.codegen as cg 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 ( from esphome.const import (
CONF_DOMAIN, CONF_DOMAIN,
CONF_ID, CONF_ID,
@ -12,9 +19,17 @@ from esphome.const import (
CONF_SUBNET, CONF_SUBNET,
CONF_DNS1, CONF_DNS1,
CONF_DNS2, 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.core import CORE, coroutine_with_priority
from esphome.components.network import IPAddress from esphome.components.network import IPAddress
from esphome.components.spi import get_spi_interface, CONF_INTERFACE_INDEX
CONFLICTS_WITH = ["wifi"] CONFLICTS_WITH = ["wifi"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
@ -27,6 +42,8 @@ CONF_MDIO_PIN = "mdio_pin"
CONF_CLK_MODE = "clk_mode" CONF_CLK_MODE = "clk_mode"
CONF_POWER_PIN = "power_pin" CONF_POWER_PIN = "power_pin"
CONF_CLOCK_SPEED = "clock_speed"
EthernetType = ethernet_ns.enum("EthernetType") EthernetType = ethernet_ns.enum("EthernetType")
ETHERNET_TYPES = { ETHERNET_TYPES = {
"LAN8720": EthernetType.ETHERNET_TYPE_LAN8720, "LAN8720": EthernetType.ETHERNET_TYPE_LAN8720,
@ -36,8 +53,11 @@ ETHERNET_TYPES = {
"JL1101": EthernetType.ETHERNET_TYPE_JL1101, "JL1101": EthernetType.ETHERNET_TYPE_JL1101,
"KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081, "KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081,
"KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA, "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_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") emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t")
CLK_MODES = { CLK_MODES = {
@ -84,11 +104,22 @@ def _validate(config):
return 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.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_MDC_PIN): pins.internal_gpio_output_pin_number,
cv.Required(CONF_MDIO_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( cv.Optional(CONF_CLK_MODE, default="GPIO0_IN"): cv.enum(
@ -96,19 +127,64 @@ CONFIG_SCHEMA = cv.All(
), ),
cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), 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_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 " SPI_SCHEMA = BASE_SCHEMA.extend(
"new mdns component instead." 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,
"W5500": SPI_SCHEMA,
},
upper=True,
),
_validate, _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): def manual_ip(config):
return cg.StructInitializer( return cg.StructInitializer(
ManualIP, ManualIP,
@ -125,15 +201,31 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
cg.add(var.set_phy_addr(config[CONF_PHY_ADDR])) if config[CONF_TYPE] == "W5500":
cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) cg.add(var.set_clk_pin(config[CONF_CLK_PIN]))
cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN])) cg.add(var.set_miso_pin(config[CONF_MISO_PIN]))
cg.add(var.set_type(config[CONF_TYPE])) cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN]))
cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]])) cg.add(var.set_cs_pin(config[CONF_CS_PIN]))
cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) 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_define("USE_ETHERNET_SPI")
cg.add(var.set_power_pin(config[CONF_POWER_PIN])) 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: if CONF_MANUAL_IP in config:
cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP])))

View File

@ -9,6 +9,11 @@
#include <lwip/dns.h> #include <lwip/dns.h>
#include "esp_event.h" #include "esp_event.h"
#ifdef USE_ETHERNET_SPI
#include <driver/gpio.h>
#include <driver/spi_master.h>
#endif
namespace esphome { namespace esphome {
namespace ethernet { namespace ethernet {
@ -33,6 +38,36 @@ void EthernetComponent::setup() {
} }
esp_err_t err; 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(); err = esp_netif_init();
ESPHL_ERROR_CHECK(err, "ETH netif init error"); ESPHL_ERROR_CHECK(err, "ETH netif init error");
err = esp_event_loop_create_default(); err = esp_event_loop_create_default();
@ -43,10 +78,40 @@ void EthernetComponent::setup() {
// Init MAC and PHY configs to default // Init MAC and PHY configs to default
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); 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.phy_addr = this->phy_addr_;
phy_config.reset_gpio_num = this->power_pin_; phy_config.reset_gpio_num = this->power_pin_;
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
#if ESP_IDF_VERSION_MAJOR >= 5 #if ESP_IDF_VERSION_MAJOR >= 5
eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_; 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_; mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_;
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
#endif
#endif #endif
switch (this->type_) { switch (this->type_) {
#if CONFIG_ETH_USE_ESP32_EMAC
case ETHERNET_TYPE_LAN8720: { case ETHERNET_TYPE_LAN8720: {
this->phy_ = esp_eth_phy_new_lan87xx(&phy_config); this->phy_ = esp_eth_phy_new_lan87xx(&phy_config);
break; break;
@ -94,6 +161,13 @@ void EthernetComponent::setup() {
#endif #endif
break; break;
} }
#endif
#ifdef USE_ETHERNET_SPI
case ETHERNET_TYPE_W5500: {
this->phy_ = esp_eth_phy_new_w5500(&phy_config);
break;
}
#endif
default: { default: {
this->mark_failed(); this->mark_failed();
return; return;
@ -105,10 +179,18 @@ void EthernetComponent::setup() {
err = esp_eth_driver_install(&eth_config, &this->eth_handle_); err = esp_eth_driver_install(&eth_config, &this->eth_handle_);
ESPHL_ERROR_CHECK(err, "ETH driver install error"); ESPHL_ERROR_CHECK(err, "ETH driver install error");
#ifndef USE_ETHERNET_SPI
if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) { 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. // KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide.
this->ksz8081_set_clock_reference_(mac); 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 */ /* attach Ethernet driver to TCP/IP stack */
err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_)); 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"); 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); 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"); 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); 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"); ESPHL_ERROR_CHECK(err, "GOT IPv6 event handler register error");
#endif /* ENABLE_IPV6 */ #endif /* USE_NETWORK_IPV6 */
/* start Ethernet driver state machine */ /* start Ethernet driver state machine */
err = esp_eth_start(this->eth_handle_); err = esp_eth_start(this->eth_handle_);
@ -165,20 +247,6 @@ void EthernetComponent::loop() {
this->state_ = EthernetComponentState::CONNECTING; this->state_ = EthernetComponentState::CONNECTING;
this->start_connect_(); 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; break;
} }
} }
@ -214,6 +282,10 @@ void EthernetComponent::dump_config() {
eth_type = "KSZ8081RNA"; eth_type = "KSZ8081RNA";
break; break;
case ETHERNET_TYPE_W5500:
eth_type = "W5500";
break;
default: default:
eth_type = "Unknown"; eth_type = "Unknown";
break; break;
@ -221,23 +293,51 @@ void EthernetComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Ethernet:"); ESP_LOGCONFIG(TAG, "Ethernet:");
this->dump_connect_params_(); 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) { if (this->power_pin_ != -1) {
ESP_LOGCONFIG(TAG, " Power Pin: %u", this->power_pin_); ESP_LOGCONFIG(TAG, " Power Pin: %u", this->power_pin_);
} }
ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_); ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_);
ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_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_); 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; } float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; }
bool EthernetComponent::can_proceed() { return this->is_connected(); } 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_ip_info_t ip;
esp_netif_get_ip_info(this->eth_netif_, &ip); esp_err_t err = esp_netif_get_ip_info(this->eth_netif_, &ip);
return network::IPAddress(&ip.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) { 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 EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
void *event_data) { 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; 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 EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
void *event_data) { void *event_data) {
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP6 (num=%" PRId32 ")", event_id); ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data;
global_eth_component->got_ipv6_ = true; ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip));
global_eth_component->ipv6_count_ += 1; 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_() { 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->connect_begin_ = millis();
this->status_set_warning(); this->status_set_warning();
@ -334,12 +447,12 @@ void EthernetComponent::start_connect_() {
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) { if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
ESPHL_ERROR_CHECK(err, "DHCPC start error"); ESPHL_ERROR_CHECK(err, "DHCPC start error");
} }
#if ENABLE_IPV6 #if USE_NETWORK_IPV6
err = esp_netif_create_ip6_linklocal(this->eth_netif_); err = esp_netif_create_ip6_linklocal(this->eth_netif_);
if (err != ESP_OK) { 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(); 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, " DNS1: %s", network::IPAddress(dns_ip1).str().c_str());
ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2).str().c_str()); ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2).str().c_str());
#if ENABLE_IPV6 #if USE_NETWORK_IPV6
if (this->ipv6_count_ > 0) { struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
esp_ip6_addr_t ip6_addr; uint8_t count = 0;
esp_netif_get_ip6_linklocal(this->eth_netif_, &ip6_addr); count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s);
ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(ip6_addr)); assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
for (int i = 0; i < count; i++) {
if (esp_netif_get_ip6_global(this->eth_netif_, &ip6_addr) == 0 && ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(if_ip6s[i]));
esp_netif_ip6_get_addr_type(&ip6_addr) == ESP_IP6_ADDR_IS_GLOBAL) {
ESP_LOGCONFIG(TAG, "IPv6 Addr (Global): " IPV6STR, IPV62STR(ip6_addr));
}
} }
#endif /* ENABLE_IPV6 */ #endif /* USE_NETWORK_IPV6 */
esp_err_t err; 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); 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_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_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_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_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) { 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_mode_ = clk_mode;
this->clk_gpio_ = clk_gpio; 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; } void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
std::string EthernetComponent::get_use_address() const { std::string EthernetComponent::get_use_address() const {
@ -428,6 +548,7 @@ bool EthernetComponent::powerdown() {
return true; return true;
} }
#ifndef USE_ETHERNET_SPI
void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
#define KSZ80XX_PC2R_REG_ADDR (0x1F) #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 #undef KSZ80XX_PC2R_REG_ADDR
} }
#endif
} // namespace ethernet } // namespace ethernet
} // namespace esphome } // namespace esphome

View File

@ -23,6 +23,7 @@ enum EthernetType {
ETHERNET_TYPE_JL1101, ETHERNET_TYPE_JL1101,
ETHERNET_TYPE_KSZ8081, ETHERNET_TYPE_KSZ8081,
ETHERNET_TYPE_KSZ8081RNA, ETHERNET_TYPE_KSZ8081RNA,
ETHERNET_TYPE_W5500,
}; };
struct ManualIP { struct ManualIP {
@ -50,15 +51,25 @@ class EthernetComponent : public Component {
void on_shutdown() override { powerdown(); } void on_shutdown() override { powerdown(); }
bool is_connected(); 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_phy_addr(uint8_t phy_addr);
void set_power_pin(int power_pin); void set_power_pin(int power_pin);
void set_mdc_pin(uint8_t mdc_pin); void set_mdc_pin(uint8_t mdc_pin);
void set_mdio_pin(uint8_t mdio_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); 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); void set_manual_ip(const ManualIP &manual_ip);
network::IPAddress get_ip_address(); network::IPAddresses get_ip_addresses();
std::string get_use_address() const; std::string get_use_address() const;
void set_use_address(const std::string &use_address); void set_use_address(const std::string &use_address);
bool powerdown(); bool powerdown();
@ -76,19 +87,30 @@ class EthernetComponent : public Component {
void ksz8081_set_clock_reference_(esp_eth_mac_t *mac); void ksz8081_set_clock_reference_(esp_eth_mac_t *mac);
std::string use_address_; 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}; uint8_t phy_addr_{0};
int power_pin_{-1}; int power_pin_{-1};
uint8_t mdc_pin_{23}; uint8_t mdc_pin_{23};
uint8_t mdio_pin_{18}; uint8_t mdio_pin_{18};
EthernetType type_{ETHERNET_TYPE_UNKNOWN};
emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN};
emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO};
#endif
EthernetType type_{ETHERNET_TYPE_UNKNOWN};
optional<ManualIP> manual_ip_{}; optional<ManualIP> manual_ip_{};
bool started_{false}; bool started_{false};
bool connected_{false}; bool connected_{false};
bool got_ipv4_address_{false};
#if LWIP_IPV6 #if LWIP_IPV6
bool got_ipv6_{false};
uint8_t ipv6_count_{0}; uint8_t ipv6_count_{0};
#endif /* LWIP_IPV6 */ #endif /* LWIP_IPV6 */
EthernetComponentState state_{EthernetComponentState::STOPPED}; EthernetComponentState state_{EthernetComponentState::STOPPED};

View File

@ -12,19 +12,30 @@ namespace ethernet_info {
class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor { class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor {
public: public:
void update() override { void update() override {
auto ip = ethernet::global_eth_component->get_ip_address(); auto ips = ethernet::global_eth_component->get_ip_addresses();
if (ip != this->last_ip_) { if (ips != this->last_ips_) {
this->last_ip_ = ip; this->last_ips_ = ips;
this->publish_state(network::IPAddress(ip).str()); 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; } float get_setup_priority() const override { return setup_priority::ETHERNET; }
std::string unique_id() override { return get_mac_address() + "-ethernetinfo"; } std::string unique_id() override { return get_mac_address() + "-ethernetinfo"; }
void dump_config() override; void dump_config() override;
void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; }
protected: protected:
network::IPAddress last_ip_; network::IPAddresses last_ips_;
std::array<text_sensor::TextSensor *, 5> ip_sensors_;
}; };
} // namespace ethernet_info } // namespace ethernet_info

View File

@ -18,17 +18,25 @@ CONFIG_SCHEMA = cv.Schema(
{ {
cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema(
IPAddressEsthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC 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): 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))

View File

@ -1,7 +1,13 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import i2c 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 import automation
from esphome.automation import maybe_simple_id from esphome.automation import maybe_simple_id
@ -9,7 +15,6 @@ CODEOWNERS = ["@carlos-sarmiento"]
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]
MULTI_CONF = True MULTI_CONF = True
CONF_VOLUME = "volume"
CONF_VOLUME_PER_MINUTE = "volume_per_minute" CONF_VOLUME_PER_MINUTE = "volume_per_minute"
ezo_pmp_ns = cg.esphome_ns.namespace("ezo_pmp") ezo_pmp_ns = cg.esphome_ns.namespace("ezo_pmp")

View File

@ -15,7 +15,10 @@ from esphome.const import (
CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_COMMAND_TOPIC,
CONF_SPEED_STATE_TOPIC, CONF_SPEED_STATE_TOPIC,
CONF_OFF_SPEED_CYCLE, CONF_OFF_SPEED_CYCLE,
CONF_ON_DIRECTION_SET,
CONF_ON_OSCILLATING_SET,
CONF_ON_SPEED_SET, CONF_ON_SPEED_SET,
CONF_ON_STATE,
CONF_ON_TURN_OFF, CONF_ON_TURN_OFF,
CONF_ON_TURN_ON, CONF_ON_TURN_ON,
CONF_ON_PRESET_SET, CONF_ON_PRESET_SET,
@ -55,11 +58,22 @@ TurnOffAction = fan_ns.class_("TurnOffAction", automation.Action)
ToggleAction = fan_ns.class_("ToggleAction", automation.Action) ToggleAction = fan_ns.class_("ToggleAction", automation.Action)
CycleSpeedAction = fan_ns.class_("CycleSpeedAction", 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()) FanTurnOnTrigger = fan_ns.class_("FanTurnOnTrigger", automation.Trigger.template())
FanTurnOffTrigger = fan_ns.class_("FanTurnOffTrigger", 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 = fan_ns.class_(
"FanPresetSetTrigger", automation.Trigger.template() "FanPresetSetTrigger", automation.Trigger.template(cg.std_string)
) )
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template()) 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.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic 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.Optional(CONF_ON_TURN_ON): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger), 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.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.Optional(CONF_ON_SPEED_SET): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger), 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]) 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, []): for conf in config.get(CONF_ON_TURN_ON, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_TURN_OFF, []): for conf in config.get(CONF_ON_TURN_OFF, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) 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, []): for conf in config.get(CONF_ON_SPEED_SET, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) 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, []): for conf in config.get(CONF_ON_PRESET_SET, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) 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): async def register_fan(var, config):

Some files were not shown because too many files have changed in this diff Show More