mirror of
https://github.com/esphome/esphome.git
synced 2024-11-23 15:38:11 +01:00
Merge branch 'remoteDev' into esp-adf-take2
This commit is contained in:
commit
0d21eca6c0
891 changed files with 27049 additions and 1210 deletions
4
.github/actions/build-image/action.yaml
vendored
4
.github/actions/build-image/action.yaml
vendored
|
@ -36,7 +36,7 @@ runs:
|
|||
|
||||
- name: Build and push to ghcr by digest
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@v5.2.0
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
|
@ -67,7 +67,7 @@ runs:
|
|||
|
||||
- name: Build and push to dockerhub by digest
|
||||
id: build-dockerhub
|
||||
uses: docker/build-push-action@v5.2.0
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
|
|
4
.github/actions/restore-python/action.yml
vendored
4
.github/actions/restore-python/action.yml
vendored
|
@ -17,12 +17,12 @@ runs:
|
|||
steps:
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.0.0
|
||||
uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.1
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
|
|
80
.github/workflows/ci-api-proto.yml
vendored
Normal file
80
.github/workflows/ci-api-proto.yml
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
name: API Proto CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "esphome/components/api/api.proto"
|
||||
- "esphome/components/api/api_pb2.cpp"
|
||||
- "esphome/components/api/api_pb2.h"
|
||||
- "esphome/components/api/api_pb2_service.cpp"
|
||||
- "esphome/components/api/api_pb2_service.h"
|
||||
- "script/api_protobuf/api_protobuf.py"
|
||||
- ".github/workflows/ci-api-proto.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check generated files
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install apt dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt-cache show protobuf-compiler
|
||||
sudo apt install -y protobuf-compiler
|
||||
protoc --version
|
||||
- name: Install python dependencies
|
||||
run: pip install aioesphomeapi -c requirements.txt -r requirements_dev.txt
|
||||
- name: Generate files
|
||||
run: script/api_protobuf/api_protobuf.py
|
||||
- name: Check for changes
|
||||
run: |
|
||||
if ! git diff --quiet; then
|
||||
echo "## Job Failed" | tee -a $GITHUB_STEP_SUMMARY
|
||||
echo "You have altered the generated proto files but they do not match what is expected." | tee -a $GITHUB_STEP_SUMMARY
|
||||
echo "Please run 'script/api_protobuf/api_protobuf.py' and commit the changes." | tee -a $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
- if: failure()
|
||||
name: Review PR
|
||||
uses: actions/github-script@v7.0.1
|
||||
with:
|
||||
script: |
|
||||
await github.rest.pulls.createReview({
|
||||
pull_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
event: 'REQUEST_CHANGES',
|
||||
body: 'You have altered the generated proto files but they do not match what is expected.\nPlease run "script/api_protobuf/api_protobuf.py" and commit the changes.'
|
||||
})
|
||||
- if: success()
|
||||
name: Dismiss review
|
||||
uses: actions/github-script@v7.0.1
|
||||
with:
|
||||
script: |
|
||||
let reviews = await github.rest.pulls.listReviews({
|
||||
pull_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo
|
||||
});
|
||||
for (let review of reviews.data) {
|
||||
if (review.user.login === 'github-actions[bot]' && review.state === 'CHANGES_REQUESTED') {
|
||||
await github.rest.pulls.dismissReview({
|
||||
pull_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
review_id: review.id,
|
||||
message: 'Files now match the expected proto files.'
|
||||
});
|
||||
}
|
||||
}
|
6
.github/workflows/ci-docker.yml
vendored
6
.github/workflows/ci-docker.yml
vendored
|
@ -2,7 +2,7 @@
|
|||
name: CI for docker images
|
||||
|
||||
# Only run when docker paths change
|
||||
# yamllint disable-line rule:truthy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev, beta, release]
|
||||
|
@ -42,11 +42,11 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.0.0
|
||||
uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.1.0
|
||||
uses: docker/setup-buildx-action@v3.2.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
|
||||
|
|
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
name: CI
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
push:
|
||||
branches: [dev, beta, release]
|
||||
|
@ -42,12 +41,12 @@ jobs:
|
|||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.0.0
|
||||
uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.0.1
|
||||
uses: actions/cache@v4.0.2
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
|
@ -367,7 +366,7 @@ jobs:
|
|||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Cache platformio
|
||||
uses: actions/cache@v4.0.1
|
||||
uses: actions/cache@v4.0.2
|
||||
with:
|
||||
path: ~/.platformio
|
||||
# yamllint disable-line rule:line-length
|
||||
|
@ -398,6 +397,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- common
|
||||
if: github.event_name == 'pull_request'
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
|
@ -406,10 +406,14 @@ jobs:
|
|||
with:
|
||||
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
|
||||
fetch-depth: 500
|
||||
- name: Fetch dev branch
|
||||
- name: Get target branch
|
||||
id: target-branch
|
||||
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*
|
||||
git merge-base refs/remotes/origin/dev HEAD
|
||||
echo "branch=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT
|
||||
- name: Fetch ${{ steps.target-branch.outputs.branch }} branch
|
||||
run: |
|
||||
git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/${{ steps.target-branch.outputs.branch }}:refs/remotes/origin/${{ steps.target-branch.outputs.branch }}
|
||||
git merge-base refs/remotes/origin/${{ steps.target-branch.outputs.branch }} HEAD
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
@ -419,7 +423,7 @@ jobs:
|
|||
id: set-matrix
|
||||
run: |
|
||||
. 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:
|
||||
name: Component test ${{ matrix.file }}
|
||||
|
@ -427,7 +431,7 @@ jobs:
|
|||
needs:
|
||||
- common
|
||||
- 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:
|
||||
fail-fast: false
|
||||
max-parallel: 2
|
||||
|
|
1
.github/workflows/lock.yml
vendored
1
.github/workflows/lock.yml
vendored
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
name: Lock
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 0 * * *"
|
||||
|
|
1
.github/workflows/needs-docs.yml
vendored
1
.github/workflows/needs-docs.yml
vendored
|
@ -1,6 +1,5 @@
|
|||
name: Needs Docs
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, unlabeled]
|
||||
|
|
17
.github/workflows/release.yml
vendored
17
.github/workflows/release.yml
vendored
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
name: Publish Release
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
|
@ -45,7 +44,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.0.0
|
||||
uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Set up python environment
|
||||
|
@ -80,23 +79,23 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.0.0
|
||||
uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.1.0
|
||||
uses: docker/setup-buildx-action@v3.2.0
|
||||
- name: Set up QEMU
|
||||
if: matrix.platform != 'linux/amd64'
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
@ -163,17 +162,17 @@ jobs:
|
|||
name: digests-${{ matrix.image.target }}-${{ matrix.registry }}
|
||||
path: /tmp/digests
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.1.0
|
||||
uses: docker/setup-buildx-action@v3.2.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
if: matrix.registry == 'dockerhub'
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
if: matrix.registry == 'ghcr'
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
|
1
.github/workflows/stale.yml
vendored
1
.github/workflows/stale.yml
vendored
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
name: Stale
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 0 * * *"
|
||||
|
|
5
.github/workflows/sync-device-classes.yml
vendored
5
.github/workflows/sync-device-classes.yml
vendored
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
name: Synchronise Device Classes from Home Assistant
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
|
@ -23,7 +22,7 @@ jobs:
|
|||
path: lib/home-assistant
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5.0.0
|
||||
uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: 3.11
|
||||
|
||||
|
@ -37,7 +36,7 @@ jobs:
|
|||
python ./script/sync-device_class.py
|
||||
|
||||
- name: Commit changes
|
||||
uses: peter-evans/create-pull-request@v6.0.1
|
||||
uses: peter-evans/create-pull-request@v6.0.2
|
||||
with:
|
||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||
committer: esphomebot <esphome@nabucasa.com>
|
||||
|
|
1
.github/workflows/yaml-lint.yml
vendored
1
.github/workflows/yaml-lint.yml
vendored
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
name: YAML lint
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
push:
|
||||
branches: [dev, beta, release]
|
||||
|
|
|
@ -16,3 +16,4 @@ rules:
|
|||
indent-sequences: true
|
||||
check-multi-line-strings: false
|
||||
line-length: disable
|
||||
truthy: disable
|
||||
|
|
13
CODEOWNERS
13
CODEOWNERS
|
@ -42,6 +42,7 @@ esphome/components/as5600/* @ammmze
|
|||
esphome/components/as5600/sensor/* @ammmze
|
||||
esphome/components/as7341/* @mrgnr
|
||||
esphome/components/async_tcp/* @OttoWinter
|
||||
esphome/components/at581x/* @X-Ryl669
|
||||
esphome/components/atc_mithermometer/* @ahpohl
|
||||
esphome/components/atm90e26/* @danieltwagner
|
||||
esphome/components/b_parasite/* @rbaron
|
||||
|
@ -86,6 +87,7 @@ esphome/components/cst816/* @clydebarrow
|
|||
esphome/components/ct_clamp/* @jesserockz
|
||||
esphome/components/current_based/* @djwmarcx
|
||||
esphome/components/dac7678/* @NickB1
|
||||
esphome/components/daikin_arc/* @MagicBear
|
||||
esphome/components/daikin_brc/* @hagak
|
||||
esphome/components/daly_bms/* @s1lvi0
|
||||
esphome/components/dashboard_import/* @esphome/core
|
||||
|
@ -103,6 +105,7 @@ esphome/components/duty_time/* @dudanov
|
|||
esphome/components/ee895/* @Stock-M
|
||||
esphome/components/ektf2232/touchscreen/* @jesserockz
|
||||
esphome/components/emc2101/* @ellull
|
||||
esphome/components/emmeti/* @E440QF
|
||||
esphome/components/ens160/* @vincentscode
|
||||
esphome/components/ens210/* @itn3rd77
|
||||
esphome/components/esp32/* @esphome/core
|
||||
|
@ -153,6 +156,7 @@ esphome/components/honeywellabp2_i2c/* @jpfaff
|
|||
esphome/components/host/* @esphome/core
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/hte501/* @Stock-M
|
||||
esphome/components/htu31d/* @betterengineering
|
||||
esphome/components/hydreon_rgxx/* @functionpointer
|
||||
esphome/components/hyt271/* @Philippe12
|
||||
esphome/components/i2c/* @esphome/core
|
||||
|
@ -171,7 +175,9 @@ esphome/components/inkplate6/* @jesserockz
|
|||
esphome/components/integration/* @OttoWinter
|
||||
esphome/components/internal_temperature/* @Mat931
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/jsn_sr04t/* @Mafus1
|
||||
esphome/components/json/* @OttoWinter
|
||||
esphome/components/kamstrup_kmp/* @cfeenstra1024
|
||||
esphome/components/key_collector/* @ssieb
|
||||
esphome/components/key_provider/* @ssieb
|
||||
esphome/components/kuntze/* @ssieb
|
||||
|
@ -270,6 +276,7 @@ esphome/components/pvvx_mithermometer/* @pasiz
|
|||
esphome/components/pylontech/* @functionpointer
|
||||
esphome/components/qmp6988/* @andrewpc
|
||||
esphome/components/qr_code/* @wjtje
|
||||
esphome/components/qspi_amoled/* @clydebarrow
|
||||
esphome/components/qwiic_pir/* @kahrendt
|
||||
esphome/components/radon_eye_ble/* @jeffeb3
|
||||
esphome/components/radon_eye_rd200/* @jeffeb3
|
||||
|
@ -283,6 +290,7 @@ esphome/components/rgbct/* @jesserockz
|
|||
esphome/components/rp2040/* @jesserockz
|
||||
esphome/components/rp2040_pio_led_strip/* @Papa-DMan
|
||||
esphome/components/rp2040_pwm/* @jesserockz
|
||||
esphome/components/rpi_dpi_rgb/* @clydebarrow
|
||||
esphome/components/rtl87xx/* @kuba2k2
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
||||
|
@ -290,6 +298,7 @@ esphome/components/scd4x/* @martgras @sjtrny
|
|||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
esphome/components/sdp3x/* @Azimath
|
||||
esphome/components/seeed_mr24hpc1/* @limengdu
|
||||
esphome/components/selec_meter/* @sourabhjaiswal
|
||||
esphome/components/select/* @esphome/core
|
||||
esphome/components/sen0321/* @notjj
|
||||
|
@ -301,6 +310,7 @@ esphome/components/sfa30/* @ghsensdev
|
|||
esphome/components/sgp40/* @SenexCrenshaw
|
||||
esphome/components/sgp4x/* @SenexCrenshaw @martgras
|
||||
esphome/components/shelly_dimmer/* @edge90 @rnauber
|
||||
esphome/components/sht3xd/* @mrtoy-me
|
||||
esphome/components/sht4x/* @sjtrny
|
||||
esphome/components/shutdown/* @esphome/core @jsuanet
|
||||
esphome/components/sigma_delta_output/* @Cat-Ion
|
||||
|
@ -334,11 +344,13 @@ esphome/components/ssd1351_spi/* @kbx81
|
|||
esphome/components/st7567_base/* @latonita
|
||||
esphome/components/st7567_i2c/* @latonita
|
||||
esphome/components/st7567_spi/* @latonita
|
||||
esphome/components/st7701s/* @clydebarrow
|
||||
esphome/components/st7735/* @SenexCrenshaw
|
||||
esphome/components/st7789v/* @kbx81
|
||||
esphome/components/st7920/* @marsjan155
|
||||
esphome/components/substitutions/* @esphome/core
|
||||
esphome/components/sun/* @OttoWinter
|
||||
esphome/components/sun_gtil2/* @Mat931
|
||||
esphome/components/switch/* @esphome/core
|
||||
esphome/components/t6615/* @tylermenezes
|
||||
esphome/components/tca9548a/* @andreashergert1984
|
||||
|
@ -379,6 +391,7 @@ esphome/components/ultrasonic/* @OttoWinter
|
|||
esphome/components/uponor_smatrix/* @kroimon
|
||||
esphome/components/vbus/* @ssieb
|
||||
esphome/components/veml3235/* @kbx81
|
||||
esphome/components/veml7700/* @latonita
|
||||
esphome/components/version/* @esphome/core
|
||||
esphome/components/voice_assistant/* @jesserockz
|
||||
esphome/components/wake_on_lan/* @willwill2will54
|
||||
|
|
|
@ -41,6 +41,7 @@ CONF_CURRENT_GAIN_A = "current_gain_a"
|
|||
CONF_CURRENT_GAIN_B = "current_gain_b"
|
||||
CONF_ACTIVE_POWER_GAIN_A = "active_power_gain_a"
|
||||
CONF_ACTIVE_POWER_GAIN_B = "active_power_gain_b"
|
||||
CONF_USE_ACCUMULATED_ENERGY_REGISTERS = "use_accumulated_energy_registers"
|
||||
PGA_GAINS = {
|
||||
"1x": 0b000,
|
||||
"2x": 0b001,
|
||||
|
@ -155,6 +156,7 @@ ADE7953_CONFIG_SCHEMA = cv.Schema(
|
|||
cv.Optional(CONF_ACTIVE_POWER_GAIN_B, default=0x400000): cv.hex_int_range(
|
||||
min=0x100000, max=0x800000
|
||||
),
|
||||
cv.Optional(CONF_USE_ACCUMULATED_ENERGY_REGISTERS, default=False): cv.boolean,
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
|
||||
|
@ -174,6 +176,9 @@ async def register_ade7953(var, config):
|
|||
cg.add(var.set_bigain(config.get(CONF_CURRENT_GAIN_B)))
|
||||
cg.add(var.set_awgain(config.get(CONF_ACTIVE_POWER_GAIN_A)))
|
||||
cg.add(var.set_bwgain(config.get(CONF_ACTIVE_POWER_GAIN_B)))
|
||||
cg.add(
|
||||
var.set_use_acc_energy_regs(config.get(CONF_USE_ACCUMULATED_ENERGY_REGISTERS))
|
||||
)
|
||||
|
||||
for key in [
|
||||
CONF_VOLTAGE,
|
||||
|
|
|
@ -6,6 +6,9 @@ namespace ade7953_base {
|
|||
|
||||
static const char *const TAG = "ade7953";
|
||||
|
||||
static const float ADE_POWER_FACTOR = 154.0f;
|
||||
static const float ADE_WATTSEC_POWER_FACTOR = ADE_POWER_FACTOR * ADE_POWER_FACTOR / 3600;
|
||||
|
||||
void ADE7953::setup() {
|
||||
if (this->irq_pin_ != nullptr) {
|
||||
this->irq_pin_->setup();
|
||||
|
@ -34,6 +37,7 @@ void ADE7953::setup() {
|
|||
this->ade_read_32(BIGAIN_32, &bigain_);
|
||||
this->ade_read_32(AWGAIN_32, &awgain_);
|
||||
this->ade_read_32(BWGAIN_32, &bwgain_);
|
||||
this->last_update_ = millis();
|
||||
this->is_setup_ = true;
|
||||
});
|
||||
}
|
||||
|
@ -52,6 +56,7 @@ void ADE7953::dump_config() {
|
|||
LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_);
|
||||
LOG_SENSOR(" ", "Rective Power A Sensor", this->reactive_power_a_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power B Sensor", this->reactive_power_b_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " USE_ACC_ENERGY_REGS: %d", this->use_acc_energy_regs_);
|
||||
ESP_LOGCONFIG(TAG, " PGA_V_8: 0x%X", pga_v_);
|
||||
ESP_LOGCONFIG(TAG, " PGA_IA_8: 0x%X", pga_ia_);
|
||||
ESP_LOGCONFIG(TAG, " PGA_IB_8: 0x%X", pga_ib_);
|
||||
|
@ -85,6 +90,7 @@ void ADE7953::update() {
|
|||
|
||||
uint32_t val;
|
||||
uint16_t val_16;
|
||||
uint16_t reg;
|
||||
|
||||
// Power factor
|
||||
err = this->ade_read_16(0x010A, &val_16);
|
||||
|
@ -92,23 +98,36 @@ void ADE7953::update() {
|
|||
err = this->ade_read_16(0x010B, &val_16);
|
||||
ADE_PUBLISH(power_factor_b, (int16_t) val_16, (0x7FFF / 100.0f));
|
||||
|
||||
float pf = ADE_POWER_FACTOR;
|
||||
if (this->use_acc_energy_regs_) {
|
||||
const uint32_t now = millis();
|
||||
const auto diff = now - this->last_update_;
|
||||
this->last_update_ = now;
|
||||
// prevent DIV/0
|
||||
pf = ADE_WATTSEC_POWER_FACTOR * (diff < 10 ? 10 : diff) / 1000;
|
||||
ESP_LOGVV(TAG, "ADE7953::update() diff=%d pf=%f", diff, pf);
|
||||
}
|
||||
|
||||
// Apparent power
|
||||
err = this->ade_read_32(0x0310, &val);
|
||||
ADE_PUBLISH(apparent_power_a, (int32_t) val, 154.0f);
|
||||
err = this->ade_read_32(0x0311, &val);
|
||||
ADE_PUBLISH(apparent_power_b, (int32_t) val, 154.0f);
|
||||
reg = this->use_acc_energy_regs_ ? 0x0322 : 0x0310;
|
||||
err = this->ade_read_32(reg, &val);
|
||||
ADE_PUBLISH(apparent_power_a, (int32_t) val, pf);
|
||||
err = this->ade_read_32(reg + 1, &val);
|
||||
ADE_PUBLISH(apparent_power_b, (int32_t) val, pf);
|
||||
|
||||
// Active power
|
||||
err = this->ade_read_32(0x0312, &val);
|
||||
ADE_PUBLISH(active_power_a, (int32_t) val, 154.0f);
|
||||
err = this->ade_read_32(0x0313, &val);
|
||||
ADE_PUBLISH(active_power_b, (int32_t) val, 154.0f);
|
||||
reg = this->use_acc_energy_regs_ ? 0x031E : 0x0312;
|
||||
err = this->ade_read_32(reg, &val);
|
||||
ADE_PUBLISH(active_power_a, (int32_t) val, pf);
|
||||
err = this->ade_read_32(reg + 1, &val);
|
||||
ADE_PUBLISH(active_power_b, (int32_t) val, pf);
|
||||
|
||||
// Reactive power
|
||||
err = this->ade_read_32(0x0314, &val);
|
||||
ADE_PUBLISH(reactive_power_a, (int32_t) val, 154.0f);
|
||||
err = this->ade_read_32(0x0315, &val);
|
||||
ADE_PUBLISH(reactive_power_b, (int32_t) val, 154.0f);
|
||||
reg = this->use_acc_energy_regs_ ? 0x0320 : 0x0314;
|
||||
err = this->ade_read_32(reg, &val);
|
||||
ADE_PUBLISH(reactive_power_a, (int32_t) val, pf);
|
||||
err = this->ade_read_32(reg + 1, &val);
|
||||
ADE_PUBLISH(reactive_power_b, (int32_t) val, pf);
|
||||
|
||||
// Current
|
||||
err = this->ade_read_32(0x031A, &val);
|
||||
|
|
|
@ -52,6 +52,8 @@ class ADE7953 : public PollingComponent, public sensor::Sensor {
|
|||
void set_awgain(uint32_t awgain) { awgain_ = awgain; }
|
||||
void set_bwgain(uint32_t bwgain) { bwgain_ = bwgain; }
|
||||
|
||||
void set_use_acc_energy_regs(bool use_acc_energy_regs) { use_acc_energy_regs_ = use_acc_energy_regs; }
|
||||
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; }
|
||||
|
||||
|
@ -103,6 +105,8 @@ class ADE7953 : public PollingComponent, public sensor::Sensor {
|
|||
uint32_t bigain_;
|
||||
uint32_t awgain_;
|
||||
uint32_t bwgain_;
|
||||
bool use_acc_energy_regs_{false};
|
||||
uint32_t last_update_;
|
||||
|
||||
virtual bool ade_write_8(uint16_t reg, uint8_t value) = 0;
|
||||
|
||||
|
|
|
@ -13,29 +13,29 @@ void AdE7953I2c::dump_config() {
|
|||
ade7953_base::ADE7953::dump_config();
|
||||
}
|
||||
bool AdE7953I2c::ade_write_8(uint16_t reg, uint8_t value) {
|
||||
std::vector<uint8_t> data(3);
|
||||
data.push_back(reg >> 8);
|
||||
data.push_back(reg >> 0);
|
||||
data.push_back(value);
|
||||
return this->write(data.data(), data.size()) != i2c::ERROR_OK;
|
||||
uint8_t data[3];
|
||||
data[0] = reg >> 8;
|
||||
data[1] = reg >> 0;
|
||||
data[2] = value;
|
||||
return this->write(data, 3) != i2c::ERROR_OK;
|
||||
}
|
||||
bool AdE7953I2c::ade_write_16(uint16_t reg, uint16_t value) {
|
||||
std::vector<uint8_t> data(4);
|
||||
data.push_back(reg >> 8);
|
||||
data.push_back(reg >> 0);
|
||||
data.push_back(value >> 8);
|
||||
data.push_back(value >> 0);
|
||||
return this->write(data.data(), data.size()) != i2c::ERROR_OK;
|
||||
uint8_t data[4];
|
||||
data[0] = reg >> 8;
|
||||
data[1] = reg >> 0;
|
||||
data[2] = value >> 8;
|
||||
data[3] = value >> 0;
|
||||
return this->write(data, 4) != i2c::ERROR_OK;
|
||||
}
|
||||
bool AdE7953I2c::ade_write_32(uint16_t reg, uint32_t value) {
|
||||
std::vector<uint8_t> data(6);
|
||||
data.push_back(reg >> 8);
|
||||
data.push_back(reg >> 0);
|
||||
data.push_back(value >> 24);
|
||||
data.push_back(value >> 16);
|
||||
data.push_back(value >> 8);
|
||||
data.push_back(value >> 0);
|
||||
return this->write(data.data(), data.size()) != i2c::ERROR_OK;
|
||||
uint8_t data[6];
|
||||
data[0] = reg >> 8;
|
||||
data[1] = reg >> 0;
|
||||
data[2] = value >> 24;
|
||||
data[3] = value >> 16;
|
||||
data[4] = value >> 8;
|
||||
data[5] = value >> 0;
|
||||
return this->write(data, 6) != i2c::ERROR_OK;
|
||||
}
|
||||
bool AdE7953I2c::ade_read_8(uint16_t reg, uint8_t *value) {
|
||||
uint8_t reg_data[2];
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include "aht10.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace aht10 {
|
||||
|
@ -27,7 +26,7 @@ static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
|
|||
static const uint8_t AHT10_SOFTRESET_CMD[] = {0xBA};
|
||||
|
||||
static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for initialization and temperature measurement
|
||||
static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms
|
||||
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
|
||||
|
@ -41,19 +40,18 @@ void AHT10Component::setup() {
|
|||
}
|
||||
delay(AHT10_SOFTRESET_DELAY);
|
||||
|
||||
const uint8_t *init_cmd;
|
||||
i2c::ErrorCode error_code = i2c::ERROR_INVALID_ARGUMENT;
|
||||
switch (this->variant_) {
|
||||
case AHT10Variant::AHT20:
|
||||
init_cmd = AHT20_INITIALIZE_CMD;
|
||||
ESP_LOGCONFIG(TAG, "Setting up AHT20");
|
||||
error_code = this->write(AHT20_INITIALIZE_CMD, sizeof(AHT20_INITIALIZE_CMD));
|
||||
break;
|
||||
case AHT10Variant::AHT10:
|
||||
default:
|
||||
init_cmd = AHT10_INITIALIZE_CMD;
|
||||
ESP_LOGCONFIG(TAG, "Setting up AHT10");
|
||||
error_code = this->write(AHT10_INITIALIZE_CMD, sizeof(AHT10_INITIALIZE_CMD));
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->write(init_cmd, sizeof(init_cmd)) != i2c::ERROR_OK) {
|
||||
if (error_code != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
|
@ -83,74 +81,77 @@ void AHT10Component::setup() {
|
|||
ESP_LOGV(TAG, "AHT10 initialization");
|
||||
}
|
||||
|
||||
void AHT10Component::update() {
|
||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->status_set_warning();
|
||||
void AHT10Component::restart_read_() {
|
||||
if (this->read_count_ == AHT10_ATTEMPTS) {
|
||||
this->read_count_ = 0;
|
||||
this->status_set_error("Measurements reading timed-out!");
|
||||
return;
|
||||
}
|
||||
this->read_count_++;
|
||||
this->set_timeout(AHT10_READ_DELAY, [this]() { this->read_data_(); });
|
||||
}
|
||||
|
||||
void AHT10Component::read_data_() {
|
||||
uint8_t data[6];
|
||||
uint8_t delay_ms = AHT10_DEFAULT_DELAY;
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
delay_ms = AHT10_HUMIDITY_DELAY;
|
||||
bool success = false;
|
||||
for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
|
||||
ESP_LOGVV(TAG, "Attempt %d at %6" PRIu32, i, millis());
|
||||
delay(delay_ms);
|
||||
if (this->read_count_ > 1)
|
||||
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
|
||||
if (this->read(data, 6) != i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
|
||||
continue;
|
||||
this->status_set_warning("AHT10 read failed, retrying soon");
|
||||
this->restart_read_();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
|
||||
ESP_LOGD(TAG, "AHT10 is busy, waiting...");
|
||||
} else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
|
||||
this->restart_read_();
|
||||
return;
|
||||
}
|
||||
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;
|
||||
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) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->status_set_warning();
|
||||
this->status_set_warning("Communication with AHT10 failed!");
|
||||
}
|
||||
this->restart_read_();
|
||||
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;
|
||||
}
|
||||
|
||||
if (this->read_count_ > 1)
|
||||
ESP_LOGD(TAG, "Success at %ums", (unsigned) (millis() - this->start_time_));
|
||||
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
||||
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
|
||||
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
float humidity;
|
||||
if (raw_humidity == 0) { // unrealistic value
|
||||
humidity = NAN;
|
||||
} else {
|
||||
humidity = (float) raw_humidity * 100.0f / 1048576.0f;
|
||||
}
|
||||
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
if (std::isnan(humidity)) {
|
||||
ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
|
||||
}
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
}
|
||||
this->status_clear_warning();
|
||||
this->read_count_ = 0;
|
||||
}
|
||||
void AHT10Component::update() {
|
||||
if (this->read_count_ != 0)
|
||||
return;
|
||||
this->start_time_ = millis();
|
||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||
this->status_set_warning("Communication with AHT10 failed!");
|
||||
return;
|
||||
}
|
||||
this->restart_read_();
|
||||
}
|
||||
|
||||
float AHT10Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
|
|
@ -26,6 +26,10 @@ class AHT10Component : public PollingComponent, public i2c::I2CDevice {
|
|||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
AHT10Variant variant_{};
|
||||
unsigned read_count_{};
|
||||
void read_data_();
|
||||
void restart_read_();
|
||||
uint32_t start_time_{};
|
||||
};
|
||||
|
||||
} // namespace aht10
|
||||
|
|
224
esphome/components/at581x/__init__.py
Normal file
224
esphome/components/at581x/__init__.py
Normal 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
|
195
esphome/components/at581x/at581x.cpp
Normal file
195
esphome/components/at581x/at581x.cpp
Normal 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
|
62
esphome/components/at581x/at581x.h
Normal file
62
esphome/components/at581x/at581x.h
Normal 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
|
71
esphome/components/at581x/automation.h
Normal file
71
esphome/components/at581x/automation.h
Normal 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
|
31
esphome/components/at581x/switch/__init__.py
Normal file
31
esphome/components/at581x/switch/__init__.py
Normal 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))
|
12
esphome/components/at581x/switch/rf_switch.cpp
Normal file
12
esphome/components/at581x/switch/rf_switch.cpp
Normal 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
|
15
esphome/components/at581x/switch/rf_switch.h
Normal file
15
esphome/components/at581x/switch/rf_switch.h
Normal 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
|
|
@ -117,7 +117,7 @@ void ATM90E26Component::setup() {
|
|||
this->write16_(ATM90E26_REGISTER_ADJSTART,
|
||||
0x8765); // Checks correctness of 31-3A registers and starts normal measurement if ok
|
||||
|
||||
uint16_t sys_status = this->read16_(ATM90E26_REGISTER_SYSSTATUS);
|
||||
const uint16_t sys_status = this->read16_(ATM90E26_REGISTER_SYSSTATUS);
|
||||
if (sys_status & 0xC000) { // Checksum 1 Error
|
||||
|
||||
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS1 was incorrect, expected: 0x%04X",
|
||||
|
@ -177,27 +177,27 @@ void ATM90E26Component::write16_(uint8_t a_register, uint16_t val) {
|
|||
}
|
||||
|
||||
float ATM90E26Component::get_line_current_() {
|
||||
uint16_t current = this->read16_(ATM90E26_REGISTER_IRMS);
|
||||
const uint16_t current = this->read16_(ATM90E26_REGISTER_IRMS);
|
||||
return current / 1000.0f;
|
||||
}
|
||||
|
||||
float ATM90E26Component::get_line_voltage_() {
|
||||
uint16_t voltage = this->read16_(ATM90E26_REGISTER_URMS);
|
||||
const uint16_t voltage = this->read16_(ATM90E26_REGISTER_URMS);
|
||||
return voltage / 100.0f;
|
||||
}
|
||||
|
||||
float ATM90E26Component::get_active_power_() {
|
||||
int16_t val = this->read16_(ATM90E26_REGISTER_PMEAN); // two's complement
|
||||
const int16_t val = this->read16_(ATM90E26_REGISTER_PMEAN); // two's complement
|
||||
return (float) val;
|
||||
}
|
||||
|
||||
float ATM90E26Component::get_reactive_power_() {
|
||||
int16_t val = this->read16_(ATM90E26_REGISTER_QMEAN); // two's complement
|
||||
const int16_t val = this->read16_(ATM90E26_REGISTER_QMEAN); // two's complement
|
||||
return (float) val;
|
||||
}
|
||||
|
||||
float ATM90E26Component::get_power_factor_() {
|
||||
uint16_t val = this->read16_(ATM90E26_REGISTER_POWERF); // signed
|
||||
const uint16_t val = this->read16_(ATM90E26_REGISTER_POWERF); // signed
|
||||
if (val & 0x8000) {
|
||||
return -(val & 0x7FF) / 1000.0f;
|
||||
} else {
|
||||
|
@ -206,7 +206,7 @@ float ATM90E26Component::get_power_factor_() {
|
|||
}
|
||||
|
||||
float ATM90E26Component::get_forward_active_energy_() {
|
||||
uint16_t val = this->read16_(ATM90E26_REGISTER_APENERGY);
|
||||
const uint16_t val = this->read16_(ATM90E26_REGISTER_APENERGY);
|
||||
if ((UINT32_MAX - this->cumulative_forward_active_energy_) > val) {
|
||||
this->cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
|
@ -217,7 +217,7 @@ float ATM90E26Component::get_forward_active_energy_() {
|
|||
}
|
||||
|
||||
float ATM90E26Component::get_reverse_active_energy_() {
|
||||
uint16_t val = this->read16_(ATM90E26_REGISTER_ANENERGY);
|
||||
const uint16_t val = this->read16_(ATM90E26_REGISTER_ANENERGY);
|
||||
if (UINT32_MAX - this->cumulative_reverse_active_energy_ > val) {
|
||||
this->cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
|
@ -227,7 +227,7 @@ float ATM90E26Component::get_reverse_active_energy_() {
|
|||
}
|
||||
|
||||
float ATM90E26Component::get_frequency_() {
|
||||
uint16_t freq = this->read16_(ATM90E26_REGISTER_FREQ);
|
||||
const uint16_t freq = this->read16_(ATM90E26_REGISTER_FREQ);
|
||||
return freq / 100.0f;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,75 +7,112 @@ namespace esphome {
|
|||
namespace atm90e32 {
|
||||
|
||||
static const char *const TAG = "atm90e32";
|
||||
|
||||
void ATM90E32Component::update() {
|
||||
if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
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);
|
||||
}
|
||||
|
||||
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_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].current_sensor_ != nullptr) {
|
||||
this->phase_[phase].current_ = this->get_phase_current_(phase);
|
||||
}
|
||||
if (this->phase_[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_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].power_sensor_ != nullptr) {
|
||||
this->phase_[phase].active_power_ = this->get_phase_active_power_(phase);
|
||||
}
|
||||
if (this->phase_[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_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].power_factor_sensor_ != nullptr) {
|
||||
this->phase_[phase].power_factor_ = this->get_phase_power_factor_(phase);
|
||||
}
|
||||
if (this->phase_[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_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].reactive_power_sensor_ != nullptr) {
|
||||
this->phase_[phase].reactive_power_ = this->get_phase_reactive_power_(phase);
|
||||
}
|
||||
if (this->phase_[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_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].forward_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[phase].forward_active_energy_ = this->get_phase_forward_active_energy_(phase);
|
||||
}
|
||||
if (this->phase_[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_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[phase].reverse_active_energy_ = this->get_phase_reverse_active_energy_(phase);
|
||||
}
|
||||
if (this->phase_[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_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].phase_angle_sensor_ != nullptr) {
|
||||
this->phase_[phase].phase_angle_ = this->get_phase_angle_(phase);
|
||||
}
|
||||
if (this->phase_[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_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr) {
|
||||
this->phase_[phase].harmonic_active_power_ = this->get_phase_harmonic_active_power_(phase);
|
||||
}
|
||||
if (this->phase_[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_());
|
||||
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);
|
||||
}
|
||||
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_());
|
||||
// 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->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_());
|
||||
|
@ -83,6 +120,15 @@ void ATM90E32Component::update() {
|
|||
if (this->chip_temperature_sensor_ != nullptr) {
|
||||
this->chip_temperature_sensor_->publish_state(this->get_chip_temperature_());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::update() {
|
||||
if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
this->set_publish_interval_flag_(true);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
|
@ -101,13 +147,16 @@ void ATM90E32Component::setup() {
|
|||
}
|
||||
|
||||
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
|
||||
delay(6); // Wait for the minimum 5ms + 1ms
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
|
||||
this->write16_(ATM90E32_REGISTER_METEREN, 0x0001); // Enable Metering
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != 0x0001) {
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != 0x55AA) {
|
||||
ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->write16_(ATM90E32_REGISTER_METEREN, 0x0001); // Enable Metering
|
||||
this->write16_(ATM90E32_REGISTER_SAGPEAKDETCFG, 0xFF3F); // Peak Detector time ms (15:8), Sag Period ms (7:0)
|
||||
this->write16_(ATM90E32_REGISTER_PLCONSTH, 0x0861); // PL Constant MSB (default) = 140625000
|
||||
this->write16_(ATM90E32_REGISTER_PLCONSTL, 0xC468); // PL Constant LSB (default)
|
||||
this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654); // ZX2, ZX1, ZX0 pin config
|
||||
|
@ -115,14 +164,33 @@ void ATM90E32Component::setup() {
|
|||
this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_); // PGA Gain Configuration for Current Channels
|
||||
this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x1D4C); // All Active Startup Power Threshold - 0.02A/0.00032 = 7500
|
||||
this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
|
||||
this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
|
||||
this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
|
||||
this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10%
|
||||
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[0].volt_gain_); // A Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[0].ct_gain_); // A line current gain
|
||||
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[1].volt_gain_); // B Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[1].ct_gain_); // B line current gain
|
||||
this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[2].volt_gain_); // C Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[2].ct_gain_); // C line current gain
|
||||
// Setup voltage and current calibration offsets for PHASE A
|
||||
this->phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA);
|
||||
this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // A Voltage offset
|
||||
this->phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA);
|
||||
this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // A Current offset
|
||||
// Setup voltage and current gain for PHASE A
|
||||
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[PHASEA].voltage_gain_); // A Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[PHASEA].ct_gain_); // A line current gain
|
||||
// Setup voltage and current calibration offsets for PHASE B
|
||||
this->phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB);
|
||||
this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // B Voltage offset
|
||||
this->phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB);
|
||||
this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // B Current offset
|
||||
// Setup voltage and current gain for PHASE B
|
||||
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[PHASEB].voltage_gain_); // B Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[PHASEB].ct_gain_); // B line current gain
|
||||
// Setup voltage and current calibration offsets for PHASE C
|
||||
this->phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC);
|
||||
this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset
|
||||
this->phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC);
|
||||
this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset
|
||||
// Setup voltage and current gain for PHASE C
|
||||
this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[PHASEC].voltage_gain_); // C Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[PHASEC].ct_gain_); // C line current gain
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
|
||||
}
|
||||
|
||||
|
@ -133,43 +201,54 @@ void ATM90E32Component::dump_config() {
|
|||
ESP_LOGE(TAG, "Communication with ATM90E32 failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Voltage A", this->phase_[0].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current A", this->phase_[0].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power A", this->phase_[0].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF A", this->phase_[0].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[0].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[0].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power B", this->phase_[1].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF B", this->phase_[1].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[1].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[1].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power C", this->phase_[2].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF C", this->phase_[2].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[2].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[2].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage A", this->phase_[PHASEA].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current A", this->phase_[PHASEA].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power A", this->phase_[PHASEA].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power A", this->phase_[PHASEA].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF A", this->phase_[PHASEA].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[PHASEA].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[PHASEA].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Harmonic Power A", this->phase_[PHASEA].harmonic_active_power_sensor_);
|
||||
LOG_SENSOR(" ", "Phase Angle A", this->phase_[PHASEA].phase_angle_sensor_);
|
||||
LOG_SENSOR(" ", "Peak Current A", this->phase_[PHASEA].peak_current_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage B", this->phase_[PHASEB].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current B", this->phase_[PHASEB].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power B", this->phase_[PHASEB].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power B", this->phase_[PHASEB].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF B", this->phase_[PHASEB].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[PHASEB].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[PHASEB].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Harmonic Power A", this->phase_[PHASEB].harmonic_active_power_sensor_);
|
||||
LOG_SENSOR(" ", "Phase Angle A", this->phase_[PHASEB].phase_angle_sensor_);
|
||||
LOG_SENSOR(" ", "Peak Current A", this->phase_[PHASEB].peak_current_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage C", this->phase_[PHASEC].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current C", this->phase_[PHASEC].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power C", this->phase_[PHASEC].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power C", this->phase_[PHASEC].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF C", this->phase_[PHASEC].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[PHASEC].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[PHASEC].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Harmonic Power A", this->phase_[PHASEC].harmonic_active_power_sensor_);
|
||||
LOG_SENSOR(" ", "Phase Angle A", this->phase_[PHASEC].phase_angle_sensor_);
|
||||
LOG_SENSOR(" ", "Peak Current A", this->phase_[PHASEC].peak_current_sensor_);
|
||||
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
|
||||
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
|
||||
}
|
||||
float ATM90E32Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
float ATM90E32Component::get_setup_priority() const { return setup_priority::IO; }
|
||||
|
||||
// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
|
||||
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
|
||||
// Default is 143FH (20ms, 63ms)
|
||||
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
||||
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
|
||||
uint8_t addrl = (a_register & 0xFF);
|
||||
uint8_t data[2];
|
||||
uint16_t output;
|
||||
|
||||
this->enable();
|
||||
delayMicroseconds(10);
|
||||
delay_microseconds_safe(10);
|
||||
this->write_byte(addrh);
|
||||
this->write_byte(addrl);
|
||||
delayMicroseconds(4);
|
||||
this->read_array(data, 2);
|
||||
this->disable();
|
||||
|
||||
|
@ -179,9 +258,9 @@ uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
|||
}
|
||||
|
||||
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
||||
uint16_t val_h = this->read16_(addr_h);
|
||||
uint16_t val_l = this->read16_(addr_l);
|
||||
int32_t val = (val_h << 16) | val_l;
|
||||
const uint16_t val_h = this->read16_(addr_h);
|
||||
const uint16_t val_l = this->read16_(addr_l);
|
||||
const int32_t val = (val_h << 16) | val_l;
|
||||
|
||||
ESP_LOGVV(TAG,
|
||||
"read32_ addr_h 0x%04" PRIX16 " val_h 0x%04" PRIX16 " addr_l 0x%04" PRIX16 " val_l 0x%04" PRIX16
|
||||
|
@ -192,141 +271,174 @@ int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
|||
}
|
||||
|
||||
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val) {
|
||||
uint8_t addrh = (a_register >> 8) & 0x03;
|
||||
uint8_t addrl = (a_register & 0xFF);
|
||||
|
||||
ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val);
|
||||
this->enable();
|
||||
delayMicroseconds(10);
|
||||
this->write_byte(addrh);
|
||||
this->write_byte(addrl);
|
||||
delayMicroseconds(4);
|
||||
this->write_byte((val >> 8) & 0xff);
|
||||
this->write_byte(val & 0xFF);
|
||||
this->write_byte16(a_register);
|
||||
this->write_byte16(val);
|
||||
this->disable();
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != val)
|
||||
ESP_LOGW(TAG, "SPI write error 0x%04X val 0x%04X", a_register, val);
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_line_voltage_a_() {
|
||||
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSA);
|
||||
float ATM90E32Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; }
|
||||
|
||||
float ATM90E32Component::get_local_phase_current_(uint8_t phase) { return this->phase_[phase].current_; }
|
||||
|
||||
float ATM90E32Component::get_local_phase_active_power_(uint8_t phase) { return this->phase_[phase].active_power_; }
|
||||
|
||||
float ATM90E32Component::get_local_phase_reactive_power_(uint8_t phase) { return this->phase_[phase].reactive_power_; }
|
||||
|
||||
float ATM90E32Component::get_local_phase_power_factor_(uint8_t phase) { return this->phase_[phase].power_factor_; }
|
||||
|
||||
float ATM90E32Component::get_local_phase_forward_active_energy_(uint8_t phase) {
|
||||
return this->phase_[phase].forward_active_energy_;
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_local_phase_reverse_active_energy_(uint8_t phase) {
|
||||
return this->phase_[phase].reverse_active_energy_;
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_local_phase_angle_(uint8_t phase) { return this->phase_[phase].phase_angle_; }
|
||||
|
||||
float ATM90E32Component::get_local_phase_harmonic_active_power_(uint8_t phase) {
|
||||
return this->phase_[phase].harmonic_active_power_;
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_local_phase_peak_current_(uint8_t phase) { return this->phase_[phase].peak_current_; }
|
||||
|
||||
float ATM90E32Component::get_phase_voltage_(uint8_t phase) {
|
||||
const uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMS + phase);
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != voltage)
|
||||
ESP_LOGW(TAG, "SPI URMS voltage register read error.");
|
||||
return (float) voltage / 100;
|
||||
}
|
||||
float ATM90E32Component::get_line_voltage_b_() {
|
||||
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSB);
|
||||
return (float) voltage / 100;
|
||||
|
||||
float ATM90E32Component::get_phase_voltage_avg_(uint8_t phase) {
|
||||
const uint8_t reads = 10;
|
||||
uint32_t accumulation = 0;
|
||||
uint16_t voltage = 0;
|
||||
for (uint8_t i = 0; i < reads; i++) {
|
||||
voltage = this->read16_(ATM90E32_REGISTER_URMS + phase);
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != voltage)
|
||||
ESP_LOGW(TAG, "SPI URMS voltage register read error.");
|
||||
accumulation += voltage;
|
||||
}
|
||||
float ATM90E32Component::get_line_voltage_c_() {
|
||||
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSC);
|
||||
return (float) voltage / 100;
|
||||
voltage = accumulation / reads;
|
||||
this->phase_[phase].voltage_ = (float) voltage / 100;
|
||||
return this->phase_[phase].voltage_;
|
||||
}
|
||||
float ATM90E32Component::get_line_current_a_() {
|
||||
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSA);
|
||||
|
||||
float ATM90E32Component::get_phase_current_avg_(uint8_t phase) {
|
||||
const uint8_t reads = 10;
|
||||
uint32_t accumulation = 0;
|
||||
uint16_t current = 0;
|
||||
for (uint8_t i = 0; i < reads; i++) {
|
||||
current = this->read16_(ATM90E32_REGISTER_IRMS + phase);
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != current)
|
||||
ESP_LOGW(TAG, "SPI IRMS current register read error.");
|
||||
accumulation += current;
|
||||
}
|
||||
current = accumulation / reads;
|
||||
this->phase_[phase].current_ = (float) current / 1000;
|
||||
return this->phase_[phase].current_;
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_phase_current_(uint8_t phase) {
|
||||
const uint16_t current = this->read16_(ATM90E32_REGISTER_IRMS + phase);
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != current)
|
||||
ESP_LOGW(TAG, "SPI IRMS current register read error.");
|
||||
return (float) current / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_line_current_b_() {
|
||||
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSB);
|
||||
return (float) current / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_line_current_c_() {
|
||||
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSC);
|
||||
return (float) current / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_active_power_a_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_PMEANA, ATM90E32_REGISTER_PMEANALSB);
|
||||
|
||||
float ATM90E32Component::get_phase_active_power_(uint8_t phase) {
|
||||
const int val = this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_active_power_b_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_PMEANB, ATM90E32_REGISTER_PMEANBLSB);
|
||||
|
||||
float ATM90E32Component::get_phase_reactive_power_(uint8_t phase) {
|
||||
const int val = this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_active_power_c_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_PMEANC, ATM90E32_REGISTER_PMEANCLSB);
|
||||
|
||||
float ATM90E32Component::get_phase_power_factor_(uint8_t phase) {
|
||||
const int16_t powerfactor = this->read16_(ATM90E32_REGISTER_PFMEAN + phase);
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != powerfactor)
|
||||
ESP_LOGW(TAG, "SPI power factor read error.");
|
||||
return (float) powerfactor / 1000;
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_phase_forward_active_energy_(uint8_t phase) {
|
||||
const uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGY + phase);
|
||||
if ((UINT32_MAX - this->phase_[phase].cumulative_forward_active_energy_) > val) {
|
||||
this->phase_[phase].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[phase].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[phase].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_phase_reverse_active_energy_(uint8_t phase) {
|
||||
const uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGY);
|
||||
if (UINT32_MAX - this->phase_[phase].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[phase].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[phase].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[phase].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) {
|
||||
int val = this->read32_(ATM90E32_REGISTER_PMEANH + phase, ATM90E32_REGISTER_PMEANHLSB + phase);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_reactive_power_a_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_QMEANA, ATM90E32_REGISTER_QMEANALSB);
|
||||
return val * 0.00032f;
|
||||
|
||||
float ATM90E32Component::get_phase_angle_(uint8_t phase) {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0;
|
||||
return (float) (val > 180) ? val - 360.0 : val;
|
||||
}
|
||||
float ATM90E32Component::get_reactive_power_b_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_QMEANB, ATM90E32_REGISTER_QMEANBLSB);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_reactive_power_c_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_QMEANC, ATM90E32_REGISTER_QMEANCLSB);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_power_factor_a_() {
|
||||
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANA);
|
||||
return (float) pf / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_power_factor_b_() {
|
||||
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANB);
|
||||
return (float) pf / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_power_factor_c_() {
|
||||
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANC);
|
||||
return (float) pf / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_a_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA);
|
||||
if ((UINT32_MAX - this->phase_[0].cumulative_forward_active_energy_) > val) {
|
||||
this->phase_[0].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[0].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[0].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_b_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB);
|
||||
if (UINT32_MAX - this->phase_[1].cumulative_forward_active_energy_ > val) {
|
||||
this->phase_[1].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[1].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[1].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_c_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC);
|
||||
if (UINT32_MAX - this->phase_[2].cumulative_forward_active_energy_ > val) {
|
||||
this->phase_[2].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[2].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[2].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_a_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA);
|
||||
if (UINT32_MAX - this->phase_[0].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[0].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[0].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[0].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_b_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB);
|
||||
if (UINT32_MAX - this->phase_[1].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[1].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[1].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[1].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_c_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC);
|
||||
if (UINT32_MAX - this->phase_[2].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[2].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[2].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[2].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
|
||||
float ATM90E32Component::get_phase_peak_current_(uint8_t phase) {
|
||||
int16_t val = (float) this->read16_(ATM90E32_REGISTER_IPEAK + phase);
|
||||
if (!this->peak_current_signed_)
|
||||
val = abs(val);
|
||||
// phase register * phase current gain value / 1000 * 2^13
|
||||
return (float) (val * this->phase_[phase].ct_gain_ / 8192000.0);
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_frequency_() {
|
||||
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
||||
const uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
||||
return (float) freq / 100;
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_chip_temperature_() {
|
||||
uint16_t ctemp = this->read16_(ATM90E32_REGISTER_TEMP);
|
||||
const uint16_t ctemp = this->read16_(ATM90E32_REGISTER_TEMP);
|
||||
return (float) ctemp;
|
||||
}
|
||||
|
||||
uint16_t ATM90E32Component::calibrate_voltage_offset_phase(uint8_t phase) {
|
||||
const uint8_t num_reads = 5;
|
||||
uint64_t total_value = 0;
|
||||
for (int i = 0; i < num_reads; ++i) {
|
||||
const uint32_t measurement_value = read32_(ATM90E32_REGISTER_URMS + phase, ATM90E32_REGISTER_URMSLSB + phase);
|
||||
total_value += measurement_value;
|
||||
}
|
||||
const uint32_t average_value = total_value / num_reads;
|
||||
const uint32_t shifted_value = average_value >> 7;
|
||||
const uint32_t voltage_offset = ~shifted_value + 1;
|
||||
return voltage_offset & 0xFFFF; // Take the lower 16 bits
|
||||
}
|
||||
|
||||
uint16_t ATM90E32Component::calibrate_current_offset_phase(uint8_t phase) {
|
||||
const uint8_t num_reads = 5;
|
||||
uint64_t total_value = 0;
|
||||
for (int i = 0; i < num_reads; ++i) {
|
||||
const uint32_t measurement_value = read32_(ATM90E32_REGISTER_IRMS + phase, ATM90E32_REGISTER_IRMSLSB + phase);
|
||||
total_value += measurement_value;
|
||||
}
|
||||
const uint32_t average_value = total_value / num_reads;
|
||||
const uint32_t current_offset = ~average_value + 1;
|
||||
return current_offset & 0xFFFF; // Take the lower 16 bits
|
||||
}
|
||||
|
||||
} // namespace atm90e32
|
||||
} // namespace esphome
|
||||
|
|
|
@ -3,14 +3,19 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "atm90e32_reg.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace atm90e32 {
|
||||
|
||||
class ATM90E32Component : public PollingComponent,
|
||||
public spi::SPIDevice<spi::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:
|
||||
static const uint8_t PHASEA = 0;
|
||||
static const uint8_t PHASEB = 1;
|
||||
static const uint8_t PHASEC = 2;
|
||||
void loop() override;
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
@ -20,6 +25,7 @@ class ATM90E32Component : public PollingComponent,
|
|||
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
|
||||
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
|
||||
void set_reactive_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].reactive_power_sensor_ = obj; }
|
||||
void set_apparent_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].apparent_power_sensor_ = obj; }
|
||||
void set_forward_active_energy_sensor(int phase, sensor::Sensor *obj) {
|
||||
this->phase_[phase].forward_active_energy_sensor_ = obj;
|
||||
}
|
||||
|
@ -27,64 +33,94 @@ class ATM90E32Component : public PollingComponent,
|
|||
this->phase_[phase].reverse_active_energy_sensor_ = obj;
|
||||
}
|
||||
void set_power_factor_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_factor_sensor_ = obj; }
|
||||
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; }
|
||||
void set_phase_angle_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].phase_angle_sensor_ = obj; }
|
||||
void set_harmonic_active_power_sensor(int phase, sensor::Sensor *obj) {
|
||||
this->phase_[phase].harmonic_active_power_sensor_ = obj;
|
||||
}
|
||||
void set_peak_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].peak_current_sensor_ = obj; }
|
||||
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].voltage_gain_ = gain; }
|
||||
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
||||
|
||||
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
||||
void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; }
|
||||
void set_chip_temperature_sensor(sensor::Sensor *chip_temperature_sensor) {
|
||||
chip_temperature_sensor_ = chip_temperature_sensor;
|
||||
}
|
||||
void set_line_freq(int freq) { line_freq_ = freq; }
|
||||
void set_current_phases(int phases) { current_phases_ = phases; }
|
||||
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
|
||||
uint16_t calibrate_voltage_offset_phase(uint8_t /*phase*/);
|
||||
uint16_t calibrate_current_offset_phase(uint8_t /*phase*/);
|
||||
|
||||
int32_t last_periodic_millis = millis();
|
||||
|
||||
protected:
|
||||
uint16_t read16_(uint16_t a_register);
|
||||
int read32_(uint16_t addr_h, uint16_t addr_l);
|
||||
void write16_(uint16_t a_register, uint16_t val);
|
||||
|
||||
float get_line_voltage_a_();
|
||||
float get_line_voltage_b_();
|
||||
float get_line_voltage_c_();
|
||||
float get_line_current_a_();
|
||||
float get_line_current_b_();
|
||||
float get_line_current_c_();
|
||||
float get_active_power_a_();
|
||||
float get_active_power_b_();
|
||||
float get_active_power_c_();
|
||||
float get_reactive_power_a_();
|
||||
float get_reactive_power_b_();
|
||||
float get_reactive_power_c_();
|
||||
float get_power_factor_a_();
|
||||
float get_power_factor_b_();
|
||||
float get_power_factor_c_();
|
||||
float get_forward_active_energy_a_();
|
||||
float get_forward_active_energy_b_();
|
||||
float get_forward_active_energy_c_();
|
||||
float get_reverse_active_energy_a_();
|
||||
float get_reverse_active_energy_b_();
|
||||
float get_reverse_active_energy_c_();
|
||||
float get_local_phase_voltage_(uint8_t /*phase*/);
|
||||
float get_local_phase_current_(uint8_t /*phase*/);
|
||||
float get_local_phase_active_power_(uint8_t /*phase*/);
|
||||
float get_local_phase_reactive_power_(uint8_t /*phase*/);
|
||||
float get_local_phase_power_factor_(uint8_t /*phase*/);
|
||||
float get_local_phase_forward_active_energy_(uint8_t /*phase*/);
|
||||
float get_local_phase_reverse_active_energy_(uint8_t /*phase*/);
|
||||
float get_local_phase_angle_(uint8_t /*phase*/);
|
||||
float get_local_phase_harmonic_active_power_(uint8_t /*phase*/);
|
||||
float get_local_phase_peak_current_(uint8_t /*phase*/);
|
||||
float get_phase_voltage_(uint8_t /*phase*/);
|
||||
float get_phase_voltage_avg_(uint8_t /*phase*/);
|
||||
float get_phase_current_(uint8_t /*phase*/);
|
||||
float get_phase_current_avg_(uint8_t /*phase*/);
|
||||
float get_phase_active_power_(uint8_t /*phase*/);
|
||||
float get_phase_reactive_power_(uint8_t /*phase*/);
|
||||
float get_phase_power_factor_(uint8_t /*phase*/);
|
||||
float get_phase_forward_active_energy_(uint8_t /*phase*/);
|
||||
float get_phase_reverse_active_energy_(uint8_t /*phase*/);
|
||||
float get_phase_angle_(uint8_t /*phase*/);
|
||||
float get_phase_harmonic_active_power_(uint8_t /*phase*/);
|
||||
float get_phase_peak_current_(uint8_t /*phase*/);
|
||||
float get_frequency_();
|
||||
float get_chip_temperature_();
|
||||
bool get_publish_interval_flag_() { return publish_interval_flag_; };
|
||||
void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; };
|
||||
|
||||
struct ATM90E32Phase {
|
||||
uint16_t volt_gain_{7305};
|
||||
uint16_t voltage_gain_{7305};
|
||||
uint16_t ct_gain_{27961};
|
||||
uint16_t voltage_offset_{0};
|
||||
uint16_t current_offset_{0};
|
||||
float voltage_{0};
|
||||
float current_{0};
|
||||
float active_power_{0};
|
||||
float reactive_power_{0};
|
||||
float power_factor_{0};
|
||||
float forward_active_energy_{0};
|
||||
float reverse_active_energy_{0};
|
||||
float phase_angle_{0};
|
||||
float harmonic_active_power_{0};
|
||||
float peak_current_{0};
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
sensor::Sensor *reactive_power_sensor_{nullptr};
|
||||
sensor::Sensor *apparent_power_sensor_{nullptr};
|
||||
sensor::Sensor *power_factor_sensor_{nullptr};
|
||||
sensor::Sensor *forward_active_energy_sensor_{nullptr};
|
||||
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
|
||||
sensor::Sensor *phase_angle_sensor_{nullptr};
|
||||
sensor::Sensor *harmonic_active_power_sensor_{nullptr};
|
||||
sensor::Sensor *peak_current_sensor_{nullptr};
|
||||
uint32_t cumulative_forward_active_energy_{0};
|
||||
uint32_t cumulative_reverse_active_energy_{0};
|
||||
} phase_[3];
|
||||
|
||||
sensor::Sensor *freq_sensor_{nullptr};
|
||||
sensor::Sensor *chip_temperature_sensor_{nullptr};
|
||||
uint16_t pga_gain_{0x15};
|
||||
int line_freq_{60};
|
||||
int current_phases_{3};
|
||||
bool publish_interval_flag_{true};
|
||||
bool peak_current_signed_{false};
|
||||
};
|
||||
|
||||
} // namespace atm90e32
|
||||
|
|
|
@ -131,10 +131,12 @@ static const uint16_t ATM90E32_REGISTER_IOFFSETN = 0x6E; // N Current Offset
|
|||
|
||||
/* ENERGY REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYT = 0x80; // Total Forward Active
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGY = 0x81; // Forward Active Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYA = 0x81; // A Forward Active
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYB = 0x82; // B Forward Active
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYC = 0x83; // C Forward Active
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYT = 0x84; // Total Reverse Active
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGY = 0x85; // Reverse Active Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYA = 0x85; // A Reverse Active
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYB = 0x86; // B Reverse Active
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYC = 0x87; // C Reverse Active
|
||||
|
@ -172,10 +174,12 @@ static const uint16_t ATM90E32_REGISTER_ANENERGYCH = 0xAF; // C Reverse Harm. E
|
|||
|
||||
/* POWER & P.F. REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANT = 0xB0; // Total Mean Power (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEAN = 0xB1; // Mean Power Reg Base (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANA = 0xB1; // A Mean Power (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANB = 0xB2; // B Mean Power (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANC = 0xB3; // C Mean Power (P)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANT = 0xB4; // Total Mean Power (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEAN = 0xB5; // Mean Power Reg Base (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANA = 0xB5; // A Mean Power (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANB = 0xB6; // B Mean Power (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANC = 0xB7; // C Mean Power (Q)
|
||||
|
@ -184,15 +188,18 @@ static const uint16_t ATM90E32_REGISTER_SMEANA = 0xB9; // A Mean Power (S)
|
|||
static const uint16_t ATM90E32_REGISTER_SMEANB = 0xBA; // B Mean Power (S)
|
||||
static const uint16_t ATM90E32_REGISTER_SMEANC = 0xBB; // C Mean Power (S)
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEANT = 0xBC; // Mean Power Factor
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEAN = 0xBD; // Power Factor Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEANA = 0xBD; // A Power Factor
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEANB = 0xBE; // B Power Factor
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEANC = 0xBF; // C Power Factor
|
||||
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANTLSB = 0xC0; // Lower Word (Tot. Act. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANLSB = 0xC1; // Lower Word Reg Base (Active Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANALSB = 0xC1; // Lower Word (A Act. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANBLSB = 0xC2; // Lower Word (B Act. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCLSB = 0xC3; // Lower Word (C Act. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANTLSB = 0xC4; // Lower Word (Tot. React. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANLSB = 0xC5; // Lower Word Reg Base (Reactive Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANALSB = 0xC5; // Lower Word (A React. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANBLSB = 0xC6; // Lower Word (B React. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANCLSB = 0xC7; // Lower Word (C React. Power)
|
||||
|
@ -207,12 +214,15 @@ static const uint16_t ATM90E32_REGISTER_PMEANAF = 0xD1; // A Active Fund. Power
|
|||
static const uint16_t ATM90E32_REGISTER_PMEANBF = 0xD2; // B Active Fund. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCF = 0xD3; // C Active Fund. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANTH = 0xD4; // Total Active Harm. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANH = 0xD5; // Active Harm. Power Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANAH = 0xD5; // A Active Harm. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANBH = 0xD6; // B Active Harm. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCH = 0xD7; // C Active Harm. Power
|
||||
static const uint16_t ATM90E32_REGISTER_URMS = 0xD9; // RMS Voltage Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_URMSA = 0xD9; // A RMS Voltage
|
||||
static const uint16_t ATM90E32_REGISTER_URMSB = 0xDA; // B RMS Voltage
|
||||
static const uint16_t ATM90E32_REGISTER_URMSC = 0xDB; // C RMS Voltage
|
||||
static const uint16_t ATM90E32_REGISTER_IRMS = 0xDD; // RMS Current Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSA = 0xDD; // A RMS Current
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSB = 0xDE; // B RMS Current
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSC = 0xDF; // C RMS Current
|
||||
|
@ -223,12 +233,15 @@ static const uint16_t ATM90E32_REGISTER_PMEANAFLSB = 0xE1; // Lower Word (A Act
|
|||
static const uint16_t ATM90E32_REGISTER_PMEANBFLSB = 0xE2; // Lower Word (B Act. Fund. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCFLSB = 0xE3; // Lower Word (C Act. Fund. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANTHLSB = 0xE4; // Lower Word (Tot. Act. Harm. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANHLSB = 0xE5; // Lower Word (A Act. Harm. Power) Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANAHLSB = 0xE5; // Lower Word (A Act. Harm. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANBHLSB = 0xE6; // Lower Word (B Act. Harm. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCHLSB = 0xE7; // Lower Word (C Act. Harm. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_URMSLSB = 0xE9; // Lower Word RMS Voltage Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_URMSALSB = 0xE9; // Lower Word (A RMS Voltage)
|
||||
static const uint16_t ATM90E32_REGISTER_URMSBLSB = 0xEA; // Lower Word (B RMS Voltage)
|
||||
static const uint16_t ATM90E32_REGISTER_URMSCLSB = 0xEB; // Lower Word (C RMS Voltage)
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSLSB = 0xED; // Lower Word RMS Current Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSALSB = 0xED; // Lower Word (A RMS Current)
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSBLSB = 0xEE; // Lower Word (B RMS Current)
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C RMS Current)
|
||||
|
@ -237,10 +250,12 @@ static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C RMS
|
|||
static const uint16_t ATM90E32_REGISTER_UPEAKA = 0xF1; // A Voltage Peak
|
||||
static const uint16_t ATM90E32_REGISTER_UPEAKB = 0xF2; // B Voltage Peak
|
||||
static const uint16_t ATM90E32_REGISTER_UPEAKC = 0xF3; // C Voltage Peak
|
||||
static const uint16_t ATM90E32_REGISTER_IPEAK = 0xF5; // Peak Current Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_IPEAKA = 0xF5; // A Current Peak
|
||||
static const uint16_t ATM90E32_REGISTER_IPEAKB = 0xF6; // B Current Peak
|
||||
static const uint16_t ATM90E32_REGISTER_IPEAKC = 0xF7; // C Current Peak
|
||||
static const uint16_t ATM90E32_REGISTER_FREQ = 0xF8; // Frequency
|
||||
static const uint16_t ATM90E32_REGISTER_PANGLE = 0xF9; // Mean Phase Angle Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_PANGLEA = 0xF9; // A Mean Phase Angle
|
||||
static const uint16_t ATM90E32_REGISTER_PANGLEB = 0xFA; // B Mean Phase Angle
|
||||
static const uint16_t ATM90E32_REGISTER_PANGLEC = 0xFB; // C Mean Phase Angle
|
||||
|
|
|
@ -9,8 +9,10 @@ from esphome.const import (
|
|||
CONF_PHASE_A,
|
||||
CONF_PHASE_B,
|
||||
CONF_PHASE_C,
|
||||
CONF_PHASE_ANGLE,
|
||||
CONF_POWER,
|
||||
CONF_POWER_FACTOR,
|
||||
CONF_APPARENT_POWER,
|
||||
CONF_FREQUENCY,
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
|
@ -25,12 +27,13 @@ from esphome.const import (
|
|||
ICON_CURRENT_AC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_AMPERE,
|
||||
UNIT_DEGREES,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HERTZ,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_WATT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_VOLT_AMPS_REACTIVE,
|
||||
UNIT_WATT,
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
|
||||
|
@ -40,6 +43,10 @@ CONF_GAIN_PGA = "gain_pga"
|
|||
CONF_CURRENT_PHASES = "current_phases"
|
||||
CONF_GAIN_VOLTAGE = "gain_voltage"
|
||||
CONF_GAIN_CT = "gain_ct"
|
||||
CONF_HARMONIC_POWER = "harmonic_power"
|
||||
CONF_PEAK_CURRENT = "peak_current"
|
||||
CONF_PEAK_CURRENT_SIGNED = "peak_current_signed"
|
||||
UNIT_DEG = "degrees"
|
||||
LINE_FREQS = {
|
||||
"50HZ": 50,
|
||||
"60HZ": 60,
|
||||
|
@ -85,6 +92,12 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
|
|||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER_FACTOR,
|
||||
|
@ -102,6 +115,24 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
|
|||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_PHASE_ANGLE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_DEGREES,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HARMONIC_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PEAK_CURRENT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
|
||||
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
|
||||
}
|
||||
|
@ -132,6 +163,7 @@ CONFIG_SCHEMA = (
|
|||
CURRENT_PHASES, upper=True
|
||||
),
|
||||
cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True),
|
||||
cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
|
@ -162,6 +194,9 @@ async def to_code(config):
|
|||
if reactive_power_config := conf.get(CONF_REACTIVE_POWER):
|
||||
sens = await sensor.new_sensor(reactive_power_config)
|
||||
cg.add(var.set_reactive_power_sensor(i, sens))
|
||||
if apparent_power_config := conf.get(CONF_APPARENT_POWER):
|
||||
sens = await sensor.new_sensor(apparent_power_config)
|
||||
cg.add(var.set_apparent_power_sensor(i, sens))
|
||||
if power_factor_config := conf.get(CONF_POWER_FACTOR):
|
||||
sens = await sensor.new_sensor(power_factor_config)
|
||||
cg.add(var.set_power_factor_sensor(i, sens))
|
||||
|
@ -171,6 +206,15 @@ async def to_code(config):
|
|||
if reverse_active_energy_config := conf.get(CONF_REVERSE_ACTIVE_ENERGY):
|
||||
sens = await sensor.new_sensor(reverse_active_energy_config)
|
||||
cg.add(var.set_reverse_active_energy_sensor(i, sens))
|
||||
if phase_angle_config := conf.get(CONF_PHASE_ANGLE):
|
||||
sens = await sensor.new_sensor(phase_angle_config)
|
||||
cg.add(var.set_phase_angle_sensor(i, sens))
|
||||
if harmonic_active_power_config := conf.get(CONF_HARMONIC_POWER):
|
||||
sens = await sensor.new_sensor(harmonic_active_power_config)
|
||||
cg.add(var.set_harmonic_active_power_sensor(i, sens))
|
||||
if peak_current_config := conf.get(CONF_PEAK_CURRENT):
|
||||
sens = await sensor.new_sensor(peak_current_config)
|
||||
cg.add(var.set_peak_current_sensor(i, sens))
|
||||
|
||||
if frequency_config := config.get(CONF_FREQUENCY):
|
||||
sens = await sensor.new_sensor(frequency_config)
|
||||
|
@ -182,3 +226,4 @@ async def to_code(config):
|
|||
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
||||
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
|
||||
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
|
||||
cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED]))
|
||||
|
|
1
esphome/components/daikin_arc/__init__.py
Normal file
1
esphome/components/daikin_arc/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@MagicBear"]
|
18
esphome/components/daikin_arc/climate.py
Normal file
18
esphome/components/daikin_arc/climate.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import climate_ir
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
AUTO_LOAD = ["climate_ir"]
|
||||
|
||||
daikin_arc_ns = cg.esphome_ns.namespace("daikin_arc")
|
||||
DaikinArcClimate = daikin_arc_ns.class_("DaikinArcClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
|
||||
{cv.GenerateID(): cv.declare_id(DaikinArcClimate)}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await climate_ir.register_climate_ir(var, config)
|
487
esphome/components/daikin_arc/daikin_arc.cpp
Normal file
487
esphome/components/daikin_arc/daikin_arc.cpp
Normal file
|
@ -0,0 +1,487 @@
|
|||
#include "daikin_arc.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "esphome/components/remote_base/remote_base.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace daikin_arc {
|
||||
|
||||
static const char *const TAG = "daikin.climate";
|
||||
|
||||
void DaikinArcClimate::setup() {
|
||||
climate_ir::ClimateIR::setup();
|
||||
|
||||
// Never send nan to HA
|
||||
if (std::isnan(this->target_humidity))
|
||||
this->target_humidity = 0;
|
||||
if (std::isnan(this->current_temperature))
|
||||
this->current_temperature = 0;
|
||||
if (std::isnan(this->current_humidity))
|
||||
this->current_humidity = 0;
|
||||
}
|
||||
|
||||
void DaikinArcClimate::transmit_query_() {
|
||||
uint8_t remote_header[8] = {0x11, 0xDA, 0x27, 0x00, 0x84, 0x87, 0x20, 0x00};
|
||||
|
||||
// Calculate checksum
|
||||
for (int i = 0; i < sizeof(remote_header) - 1; i++) {
|
||||
remote_header[sizeof(remote_header) - 1] += remote_header[i];
|
||||
}
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto *data = transmit.get_data();
|
||||
data->set_carrier_frequency(DAIKIN_IR_FREQUENCY);
|
||||
|
||||
data->mark(DAIKIN_ARC_PRE_MARK);
|
||||
data->space(DAIKIN_ARC_PRE_SPACE);
|
||||
|
||||
data->mark(DAIKIN_HEADER_MARK);
|
||||
data->space(DAIKIN_HEADER_SPACE);
|
||||
|
||||
for (uint8_t i : remote_header) {
|
||||
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
|
||||
data->mark(DAIKIN_BIT_MARK);
|
||||
bool bit = i & mask;
|
||||
data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE);
|
||||
}
|
||||
}
|
||||
data->mark(DAIKIN_BIT_MARK);
|
||||
data->space(0);
|
||||
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
void DaikinArcClimate::transmit_state() {
|
||||
// 0x11, 0xDA, 0x27, 0x00, 0xC5, 0x00, 0x00, 0xD7, 0x11, 0xDA, 0x27, 0x00,
|
||||
// 0x42, 0x49, 0x05, 0xA2,
|
||||
uint8_t remote_header[20] = {0x11, 0xDA, 0x27, 0x00, 0x02, 0xd0, 0x02, 0x03, 0x80, 0x03, 0x82, 0x30, 0x41, 0x1f, 0x82,
|
||||
0xf4,
|
||||
/* とつど */
|
||||
/* 0x13 */
|
||||
0x00, 0x24, 0x00, 0x00};
|
||||
|
||||
// 05 0 [1:3]MODE 1 [OFF TMR] [ON TMR] Power
|
||||
// 06-07 TEMP
|
||||
// 08 [0:3] SPEED [4:7] Swing
|
||||
// 09 00
|
||||
// 10 00
|
||||
// 11, 12: timer
|
||||
// 13 [0:6] 0000000 [7] POWERMODE
|
||||
// 14 0a
|
||||
// 15 c4
|
||||
// 16 [0:3] 8 00 [6:7] SENSOR WIND = 11 / NORMAL = 00
|
||||
// 17 24
|
||||
|
||||
uint8_t remote_state[19] = {
|
||||
0x11, 0xDA, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x0a, 0xC4,
|
||||
/* MODE TEMP HUMD FANH FANL
|
||||
パワフル音声応答 */
|
||||
/* ON
|
||||
0x01入 0x0a */
|
||||
/* OF
|
||||
0x00切 0x02 */
|
||||
0x80, 0x24, 0x00
|
||||
/* センサー風 */
|
||||
/* ON 0x83 */
|
||||
/* OF 0x80 */
|
||||
};
|
||||
|
||||
remote_state[5] = this->operation_mode_() | 0x08;
|
||||
remote_state[6] = this->temperature_();
|
||||
remote_state[7] = this->humidity_();
|
||||
static uint8_t last_humidity = 0x66;
|
||||
if (remote_state[7] != last_humidity && this->mode != climate::CLIMATE_MODE_OFF) {
|
||||
ESP_LOGD(TAG, "Set Humditiy: %d, %d\n", (int) this->target_humidity, (int) remote_state[7]);
|
||||
remote_header[9] |= 0x10;
|
||||
last_humidity = remote_state[7];
|
||||
}
|
||||
uint16_t fan_speed = this->fan_speed_();
|
||||
remote_state[8] = fan_speed >> 8;
|
||||
remote_state[9] = fan_speed & 0xff;
|
||||
|
||||
// Calculate checksum
|
||||
for (int i = 0; i < sizeof(remote_header) - 1; i++) {
|
||||
remote_header[sizeof(remote_header) - 1] += remote_header[i];
|
||||
}
|
||||
|
||||
// Calculate checksum
|
||||
for (int i = 0; i < DAIKIN_STATE_FRAME_SIZE - 1; i++) {
|
||||
remote_state[DAIKIN_STATE_FRAME_SIZE - 1] += remote_state[i];
|
||||
}
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto *data = transmit.get_data();
|
||||
data->set_carrier_frequency(DAIKIN_IR_FREQUENCY);
|
||||
|
||||
data->mark(DAIKIN_ARC_PRE_MARK);
|
||||
data->space(DAIKIN_ARC_PRE_SPACE);
|
||||
|
||||
data->mark(DAIKIN_HEADER_MARK);
|
||||
data->space(DAIKIN_HEADER_SPACE);
|
||||
|
||||
for (uint8_t i : remote_header) {
|
||||
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
|
||||
data->mark(DAIKIN_BIT_MARK);
|
||||
bool bit = i & mask;
|
||||
data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE);
|
||||
}
|
||||
}
|
||||
data->mark(DAIKIN_BIT_MARK);
|
||||
data->space(DAIKIN_MESSAGE_SPACE);
|
||||
|
||||
data->mark(DAIKIN_HEADER_MARK);
|
||||
data->space(DAIKIN_HEADER_SPACE);
|
||||
|
||||
for (uint8_t i : remote_state) {
|
||||
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
|
||||
data->mark(DAIKIN_BIT_MARK);
|
||||
bool bit = i & mask;
|
||||
data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE);
|
||||
}
|
||||
}
|
||||
data->mark(DAIKIN_BIT_MARK);
|
||||
data->space(0);
|
||||
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
uint8_t DaikinArcClimate::operation_mode_() {
|
||||
uint8_t operating_mode = DAIKIN_MODE_ON;
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
operating_mode |= DAIKIN_MODE_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
operating_mode |= DAIKIN_MODE_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
operating_mode |= DAIKIN_MODE_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
operating_mode |= DAIKIN_MODE_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
operating_mode |= DAIKIN_MODE_FAN;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
operating_mode = DAIKIN_MODE_OFF;
|
||||
break;
|
||||
}
|
||||
|
||||
return operating_mode;
|
||||
}
|
||||
|
||||
uint16_t DaikinArcClimate::fan_speed_() {
|
||||
uint16_t fan_speed;
|
||||
switch (this->fan_mode.value()) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed = DAIKIN_FAN_1 << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
fan_speed = DAIKIN_FAN_3 << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
fan_speed = DAIKIN_FAN_5 << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
fan_speed = DAIKIN_FAN_AUTO << 8;
|
||||
}
|
||||
|
||||
// If swing is enabled switch first 4 bits to 1111
|
||||
switch (this->swing_mode) {
|
||||
case climate::CLIMATE_SWING_VERTICAL:
|
||||
fan_speed |= 0x0F00;
|
||||
break;
|
||||
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||
fan_speed |= 0x000F;
|
||||
break;
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
fan_speed |= 0x0F0F;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return fan_speed;
|
||||
}
|
||||
|
||||
uint8_t DaikinArcClimate::temperature_() {
|
||||
// Force special temperatures depending on the mode
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
return 0x32;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
return 0xc0;
|
||||
default:
|
||||
float new_temp = clamp<float>(this->target_temperature, DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX);
|
||||
uint8_t temperature = (uint8_t) floor(new_temp);
|
||||
return temperature << 1 | (new_temp - temperature > 0 ? 0x01 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t DaikinArcClimate::humidity_() {
|
||||
if (this->target_humidity == 39) {
|
||||
return 0;
|
||||
} else if (this->target_humidity <= 40 || this->target_humidity == 44) {
|
||||
return 40;
|
||||
} else if (this->target_humidity <= 45 || this->target_humidity == 49) // 41 - 45
|
||||
{
|
||||
return 45;
|
||||
} else if (this->target_humidity <= 50 || this->target_humidity == 52) // 45 - 50
|
||||
{
|
||||
return 50;
|
||||
} else {
|
||||
return 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
climate::ClimateTraits DaikinArcClimate::traits() {
|
||||
climate::ClimateTraits traits = climate_ir::ClimateIR::traits();
|
||||
traits.set_supports_current_temperature(true);
|
||||
traits.set_supports_current_humidity(false);
|
||||
traits.set_supports_target_humidity(true);
|
||||
traits.set_visual_min_humidity(38);
|
||||
traits.set_visual_max_humidity(52);
|
||||
return traits;
|
||||
}
|
||||
|
||||
bool DaikinArcClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
uint8_t checksum = 0;
|
||||
for (int i = 0; i < (DAIKIN_STATE_FRAME_SIZE - 1); i++) {
|
||||
checksum += frame[i];
|
||||
}
|
||||
if (frame[DAIKIN_STATE_FRAME_SIZE - 1] != checksum) {
|
||||
ESP_LOGI(TAG, "checksum error");
|
||||
return false;
|
||||
}
|
||||
|
||||
char buf[DAIKIN_STATE_FRAME_SIZE * 3 + 1] = {0};
|
||||
for (size_t i = 0; i < DAIKIN_STATE_FRAME_SIZE; i++) {
|
||||
sprintf(buf, "%s%02x ", buf, frame[i]);
|
||||
}
|
||||
ESP_LOGD(TAG, "FRAME %s", buf);
|
||||
|
||||
uint8_t mode = frame[5];
|
||||
if (mode & DAIKIN_MODE_ON) {
|
||||
switch (mode & 0xF0) {
|
||||
case DAIKIN_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case DAIKIN_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case DAIKIN_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case DAIKIN_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case DAIKIN_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
}
|
||||
uint8_t temperature = frame[6];
|
||||
if (!(temperature & 0xC0)) {
|
||||
this->target_temperature = temperature >> 1;
|
||||
this->target_temperature += (temperature & 0x1) ? 0.5 : 0;
|
||||
}
|
||||
this->target_humidity = frame[7]; // 0, 40, 45, 50, 0xff
|
||||
uint8_t fan_mode = frame[8];
|
||||
uint8_t swing_mode = frame[9];
|
||||
if (fan_mode & 0xF && swing_mode & 0xF) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_BOTH;
|
||||
} else if (fan_mode & 0xF) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
|
||||
} else if (swing_mode & 0xF) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
|
||||
} else {
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
}
|
||||
switch (fan_mode & 0xF0) {
|
||||
case DAIKIN_FAN_1:
|
||||
case DAIKIN_FAN_2:
|
||||
case DAIKIN_FAN_SILENT:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case DAIKIN_FAN_3:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case DAIKIN_FAN_4:
|
||||
case DAIKIN_FAN_5:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
case DAIKIN_FAN_AUTO:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
05 0 [1:3]MODE 1 [OFF TMR] [ON TMR] Power
|
||||
06-07 TEMP
|
||||
08 [0:3] SPEED [4:7] Swing
|
||||
09 00
|
||||
10 00
|
||||
11, 12: timer
|
||||
13 [0:6] 0000000 [7] POWERMODE
|
||||
14 0a
|
||||
15 c4
|
||||
16 [0:3] 8 00 [6:7] SENSOR WIND = 11 / NORMAL = 00
|
||||
17 24
|
||||
05 06 07 08 09 10 11 12 13 14 15 16 17 18
|
||||
None FRAME 11 da 27 00 00 49 2e 00 b0 00 00 06 60 00 0a c4 80 24 11
|
||||
1H FRAME 11 da 27 00 00 4d 2e 00 b0 00 00 c6 30 00 2a c4 80 24 c5
|
||||
1H30 FRAME 11 da 27 00 00 4d 2e 00 b0 00 00 a6 32 00 2a c4 80 24 a7
|
||||
2H FRAME 11 da 27 00 00 4d 2e 00 b0 00 00 86 34 00 2a c4 80 24 89
|
||||
|
||||
*/
|
||||
this->publish_state();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
uint8_t state_frame[DAIKIN_STATE_FRAME_SIZE] = {};
|
||||
|
||||
bool valid_daikin_frame = false;
|
||||
if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) {
|
||||
valid_daikin_frame = true;
|
||||
int bytes_count = data.size() / 2 / 8;
|
||||
std::unique_ptr<char[]> buf(new char[bytes_count * 3 + 1]);
|
||||
buf[0] = '\0';
|
||||
for (size_t i = 0; i < bytes_count; i++) {
|
||||
uint8_t byte = 0;
|
||||
for (int8_t bit = 0; bit < 8; bit++) {
|
||||
if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE)) {
|
||||
byte |= 1 << bit;
|
||||
} else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) {
|
||||
valid_daikin_frame = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sprintf(buf.get(), "%s%02x ", buf.get(), byte);
|
||||
}
|
||||
ESP_LOGD(TAG, "WHOLE FRAME %s size: %d", buf.get(), data.size());
|
||||
}
|
||||
if (!valid_daikin_frame) {
|
||||
char sbuf[16 * 10 + 1];
|
||||
sbuf[0] = '\0';
|
||||
for (size_t j = 0; j < data.size(); j++) {
|
||||
if ((j - 2) % 16 == 0) {
|
||||
if (j > 0) {
|
||||
ESP_LOGD(TAG, "DATA %04x: %s", (j - 16 > 0xffff ? 0 : j - 16), sbuf);
|
||||
}
|
||||
sbuf[0] = '\0';
|
||||
}
|
||||
char type_ch = ' ';
|
||||
// debug_tolerance = 25%
|
||||
|
||||
if (DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_MARK))
|
||||
type_ch = 'P';
|
||||
if (DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_SPACE))
|
||||
type_ch = 'a';
|
||||
if (DAIKIN_DBG_LOWER(DAIKIN_HEADER_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_HEADER_MARK))
|
||||
type_ch = 'H';
|
||||
if (DAIKIN_DBG_LOWER(DAIKIN_HEADER_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_HEADER_SPACE))
|
||||
type_ch = 'h';
|
||||
if (DAIKIN_DBG_LOWER(DAIKIN_BIT_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_BIT_MARK))
|
||||
type_ch = 'B';
|
||||
if (DAIKIN_DBG_LOWER(DAIKIN_ONE_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ONE_SPACE))
|
||||
type_ch = '1';
|
||||
if (DAIKIN_DBG_LOWER(DAIKIN_ZERO_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ZERO_SPACE))
|
||||
type_ch = '0';
|
||||
|
||||
if (abs(data[j]) > 100000) {
|
||||
sprintf(sbuf, "%s%-5d[%c] ", sbuf, data[j] > 0 ? 99999 : -99999, type_ch);
|
||||
} else {
|
||||
sprintf(sbuf, "%s%-5d[%c] ", sbuf, (int) (round(data[j] / 10.) * 10), type_ch);
|
||||
}
|
||||
if (j == data.size() - 1) {
|
||||
ESP_LOGD(TAG, "DATA %04x: %s", (j - 8 > 0xffff ? 0 : j - 8), sbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.reset();
|
||||
|
||||
if (!data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) {
|
||||
ESP_LOGI(TAG, "non daikin_arc expect item");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t pos = 0; pos < DAIKIN_STATE_FRAME_SIZE; pos++) {
|
||||
uint8_t byte = 0;
|
||||
for (int8_t bit = 0; bit < 8; bit++) {
|
||||
if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE)) {
|
||||
byte |= 1 << bit;
|
||||
} else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) {
|
||||
ESP_LOGI(TAG, "non daikin_arc expect item pos: %d", pos);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
state_frame[pos] = byte;
|
||||
if (pos == 0) {
|
||||
// frame header
|
||||
if (byte != 0x11) {
|
||||
ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte);
|
||||
return false;
|
||||
}
|
||||
} else if (pos == 1) {
|
||||
// frame header
|
||||
if (byte != 0xDA) {
|
||||
ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte);
|
||||
return false;
|
||||
}
|
||||
} else if (pos == 2) {
|
||||
// frame header
|
||||
if (byte != 0x27) {
|
||||
ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte);
|
||||
return false;
|
||||
}
|
||||
} else if (pos == 3) { // NOLINT(bugprone-branch-clone)
|
||||
// frame header
|
||||
if (byte != 0x00) {
|
||||
ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte);
|
||||
return false;
|
||||
}
|
||||
} else if (pos == 4) {
|
||||
// frame type
|
||||
if (byte != 0x00) {
|
||||
ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte);
|
||||
return false;
|
||||
}
|
||||
} else if (pos == 5) {
|
||||
if (data.size() == 385) {
|
||||
/*
|
||||
11 da 27 00 00 1a 0c 04 2c 21 61 07 00 07 0c 00 18 00 0e 3c 00 6c 1b 61
|
||||
Inside Temp
|
||||
Outside Temp
|
||||
Humdidity
|
||||
|
||||
*/
|
||||
this->current_temperature = state_frame[5]; // Inside temperature
|
||||
// this->current_temperature = state_frame[6]; // Outside temperature
|
||||
this->publish_state();
|
||||
return true;
|
||||
} else if ((byte & 0x40) != 0x40) {
|
||||
ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this->parse_state_frame_(state_frame);
|
||||
}
|
||||
|
||||
void DaikinArcClimate::control(const climate::ClimateCall &call) {
|
||||
if (call.get_target_humidity().has_value()) {
|
||||
this->target_humidity = *call.get_target_humidity();
|
||||
}
|
||||
climate_ir::ClimateIR::control(call);
|
||||
}
|
||||
|
||||
} // namespace daikin_arc
|
||||
} // namespace esphome
|
76
esphome/components/daikin_arc/daikin_arc.h
Normal file
76
esphome/components/daikin_arc/daikin_arc.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/climate_ir/climate_ir.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace daikin_arc {
|
||||
|
||||
// Values for Daikin ARC43XXX IR Controllers
|
||||
// Temperature
|
||||
const uint8_t DAIKIN_TEMP_MIN = 10; // Celsius
|
||||
const uint8_t DAIKIN_TEMP_MAX = 30; // Celsius
|
||||
|
||||
// Modes
|
||||
const uint8_t DAIKIN_MODE_AUTO = 0x00;
|
||||
const uint8_t DAIKIN_MODE_COOL = 0x30;
|
||||
const uint8_t DAIKIN_MODE_HEAT = 0x40;
|
||||
const uint8_t DAIKIN_MODE_DRY = 0x20;
|
||||
const uint8_t DAIKIN_MODE_FAN = 0x60;
|
||||
const uint8_t DAIKIN_MODE_OFF = 0x00;
|
||||
const uint8_t DAIKIN_MODE_ON = 0x01;
|
||||
|
||||
// Fan Speed
|
||||
const uint8_t DAIKIN_FAN_AUTO = 0xA0;
|
||||
const uint8_t DAIKIN_FAN_SILENT = 0xB0;
|
||||
const uint8_t DAIKIN_FAN_1 = 0x30;
|
||||
const uint8_t DAIKIN_FAN_2 = 0x40;
|
||||
const uint8_t DAIKIN_FAN_3 = 0x50;
|
||||
const uint8_t DAIKIN_FAN_4 = 0x60;
|
||||
const uint8_t DAIKIN_FAN_5 = 0x70;
|
||||
|
||||
// IR Transmission
|
||||
const uint32_t DAIKIN_IR_FREQUENCY = 38000;
|
||||
const uint32_t DAIKIN_ARC_PRE_MARK = 9950;
|
||||
const uint32_t DAIKIN_ARC_PRE_SPACE = 25100;
|
||||
const uint32_t DAIKIN_HEADER_MARK = 3450;
|
||||
const uint32_t DAIKIN_HEADER_SPACE = 1760;
|
||||
const uint32_t DAIKIN_BIT_MARK = 400;
|
||||
const uint32_t DAIKIN_ONE_SPACE = 1300;
|
||||
const uint32_t DAIKIN_ZERO_SPACE = 480;
|
||||
const uint32_t DAIKIN_MESSAGE_SPACE = 35000;
|
||||
|
||||
const uint8_t DAIKIN_DBG_TOLERANCE = 25;
|
||||
#define DAIKIN_DBG_LOWER(x) ((100 - DAIKIN_DBG_TOLERANCE) * (x) / 100U)
|
||||
#define DAIKIN_DBG_UPPER(x) ((100 + DAIKIN_DBG_TOLERANCE) * (x) / 100U)
|
||||
|
||||
// State Frame size
|
||||
const uint8_t DAIKIN_STATE_FRAME_SIZE = 19;
|
||||
|
||||
class DaikinArcClimate : public climate_ir::ClimateIR {
|
||||
public:
|
||||
DaikinArcClimate()
|
||||
: climate_ir::ClimateIR(DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 0.5f, true, true,
|
||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
|
||||
climate::CLIMATE_FAN_HIGH},
|
||||
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
|
||||
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
|
||||
|
||||
void setup() override;
|
||||
|
||||
protected:
|
||||
void control(const climate::ClimateCall &call) override;
|
||||
// Transmit via IR the state of this climate controller.
|
||||
void transmit_query_();
|
||||
void transmit_state() override;
|
||||
climate::ClimateTraits traits() override;
|
||||
uint8_t operation_mode_();
|
||||
uint16_t fan_speed_();
|
||||
uint8_t temperature_();
|
||||
uint8_t humidity_();
|
||||
// Handle received IR Buffer
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||
bool parse_state_frame_(const uint8_t frame[]);
|
||||
};
|
||||
|
||||
} // namespace daikin_arc
|
||||
} // namespace esphome
|
|
@ -2,8 +2,10 @@ import base64
|
|||
import secrets
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import re
|
||||
|
||||
import requests
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
@ -11,7 +13,6 @@ import esphome.final_validate as fv
|
|||
from esphome import git
|
||||
from esphome.components.packages import validate_source_shorthand
|
||||
from esphome.const import CONF_REF, CONF_WIFI, CONF_ESPHOME, CONF_PROJECT
|
||||
from esphome.wizard import wizard_file
|
||||
from esphome.yaml_util import dump
|
||||
|
||||
dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import")
|
||||
|
@ -94,44 +95,6 @@ def import_config(
|
|||
if p.exists():
|
||||
raise FileExistsError
|
||||
|
||||
if project_name == "esphome.web":
|
||||
if "esp32c3" in import_url:
|
||||
board = "esp32-c3-devkitm-1"
|
||||
platform = "ESP32"
|
||||
elif "esp32s2" in import_url:
|
||||
board = "esp32-s2-saola-1"
|
||||
platform = "ESP32"
|
||||
elif "esp32s3" in import_url:
|
||||
board = "esp32-s3-devkitc-1"
|
||||
platform = "ESP32"
|
||||
elif "esp32" in import_url:
|
||||
board = "esp32dev"
|
||||
platform = "ESP32"
|
||||
elif "esp8266" in import_url:
|
||||
board = "esp01_1m"
|
||||
platform = "ESP8266"
|
||||
elif "pico-w" in import_url:
|
||||
board = "pico-w"
|
||||
platform = "RP2040"
|
||||
|
||||
kwargs = {
|
||||
"name": name,
|
||||
"friendly_name": friendly_name,
|
||||
"platform": platform,
|
||||
"board": board,
|
||||
"ssid": "!secret wifi_ssid",
|
||||
"psk": "!secret wifi_password",
|
||||
}
|
||||
if encryption:
|
||||
noise_psk = secrets.token_bytes(32)
|
||||
key = base64.b64encode(noise_psk).decode()
|
||||
kwargs["api_encryption_key"] = key
|
||||
|
||||
p.write_text(
|
||||
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:
|
||||
|
@ -142,7 +105,44 @@ def import_config(
|
|||
except requests.exceptions.RequestException as e:
|
||||
raise ValueError(f"Error while fetching {url}: {e}") from e
|
||||
|
||||
p.write_text(req.text, encoding="utf8")
|
||||
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}
|
||||
|
|
|
@ -81,7 +81,7 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
|
|||
#endif
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
|
||||
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
|
||||
|
||||
|
@ -121,7 +121,7 @@ void DeepSleepComponent::begin_sleep(bool manual) {
|
|||
App.run_safe_shutdown_hooks();
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
if (this->sleep_duration_.has_value())
|
||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||
if (this->wakeup_pin_ != nullptr) {
|
||||
|
@ -140,7 +140,7 @@ void DeepSleepComponent::begin_sleep(bool manual) {
|
|||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP32_VARIANT_ESP32C3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
if (this->sleep_duration_.has_value())
|
||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||
if (this->wakeup_pin_ != nullptr) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE
|
||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE, CONF_VOLUME
|
||||
from esphome.components import uart
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
@ -19,7 +19,6 @@ DFPlayerIsPlayingCondition = dfplayer_ns.class_(
|
|||
MULTI_CONF = True
|
||||
CONF_FOLDER = "folder"
|
||||
CONF_LOOP = "loop"
|
||||
CONF_VOLUME = "volume"
|
||||
CONF_EQ_PRESET = "eq_preset"
|
||||
CONF_ON_FINISHED_PLAYBACK = "on_finished_playback"
|
||||
|
||||
|
|
|
@ -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,
|
||||
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
|
||||
|
|
|
@ -258,6 +258,13 @@ class Display : public PollingComponent {
|
|||
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
|
||||
void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
|
||||
|
||||
/// Draw a straight line at the given angle based on the origin [x, y] for a specified length with the given color.
|
||||
void line_at_angle(int x, int y, int angle, int length, Color color = COLOR_ON);
|
||||
|
||||
/// Draw a straight line at the given angle based on the origin [x, y] from a specified start and stop radius with the
|
||||
/// given color.
|
||||
void line_at_angle(int x, int y, int angle, int start_radius, int stop_radius, Color color = COLOR_ON);
|
||||
|
||||
/// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
|
||||
void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
|
||||
|
||||
|
|
|
@ -60,6 +60,8 @@ void DisplayMenuComponent::left() {
|
|||
if (this->editing_) {
|
||||
this->finish_editing_();
|
||||
changed = true;
|
||||
} else {
|
||||
changed = this->leave_menu_();
|
||||
}
|
||||
break;
|
||||
case MENU_MODE_JOYSTICK:
|
||||
|
|
0
esphome/components/emmeti/__init__.py
Normal file
0
esphome/components/emmeti/__init__.py
Normal file
21
esphome/components/emmeti/climate.py
Normal file
21
esphome/components/emmeti/climate.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import climate_ir
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
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)
|
316
esphome/components/emmeti/emmeti.cpp
Normal file
316
esphome/components/emmeti/emmeti.cpp
Normal 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
|
109
esphome/components/emmeti/emmeti.h
Normal file
109
esphome/components/emmeti/emmeti.h
Normal 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
|
|
@ -83,20 +83,22 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
|
|||
# The default/recommended arduino framework version
|
||||
# - https://github.com/esp8266/Arduino/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266
|
||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 0, 2)
|
||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 1, 2)
|
||||
# The platformio/espressif8266 version to use for arduino 2 framework versions
|
||||
# - https://github.com/platformio/platform-espressif8266/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266
|
||||
ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 3)
|
||||
# for arduino 3 framework versions
|
||||
ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 2, 0)
|
||||
# for arduino 4 framework versions
|
||||
ARDUINO_4_PLATFORM_VERSION = cv.Version(4, 2, 1)
|
||||
|
||||
|
||||
def _arduino_check_versions(value):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": (cv.Version(3, 0, 2), "https://github.com/esp8266/Arduino.git"),
|
||||
"latest": (cv.Version(3, 0, 2), None),
|
||||
"dev": (cv.Version(3, 1, 2), "https://github.com/esp8266/Arduino.git"),
|
||||
"latest": (cv.Version(3, 1, 2), None),
|
||||
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
|
||||
}
|
||||
|
||||
|
@ -116,7 +118,9 @@ def _arduino_check_versions(value):
|
|||
|
||||
platform_version = value.get(CONF_PLATFORM_VERSION)
|
||||
if platform_version is None:
|
||||
if version >= cv.Version(3, 0, 0):
|
||||
if version >= cv.Version(3, 1, 0):
|
||||
platform_version = _parse_platform_version(str(ARDUINO_4_PLATFORM_VERSION))
|
||||
elif version >= cv.Version(3, 0, 0):
|
||||
platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION))
|
||||
elif version >= cv.Version(2, 5, 0):
|
||||
platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION))
|
||||
|
|
|
@ -155,6 +155,8 @@ CONFIG_SCHEMA = cv.All(
|
|||
"DP83848": RMII_SCHEMA,
|
||||
"IP101": RMII_SCHEMA,
|
||||
"JL1101": RMII_SCHEMA,
|
||||
"KSZ8081": RMII_SCHEMA,
|
||||
"KSZ8081RNA": RMII_SCHEMA,
|
||||
"W5500": SPI_SCHEMA,
|
||||
},
|
||||
upper=True,
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ADDRESS, CONF_COMMAND, CONF_ID, CONF_DURATION
|
||||
from esphome.const import (
|
||||
CONF_ADDRESS,
|
||||
CONF_COMMAND,
|
||||
CONF_ID,
|
||||
CONF_DURATION,
|
||||
CONF_VOLUME,
|
||||
)
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
|
||||
|
@ -9,7 +15,6 @@ CODEOWNERS = ["@carlos-sarmiento"]
|
|||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_VOLUME = "volume"
|
||||
CONF_VOLUME_PER_MINUTE = "volume_per_minute"
|
||||
|
||||
ezo_pmp_ns = cg.esphome_ns.namespace("ezo_pmp")
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import hashlib
|
||||
import logging
|
||||
|
||||
import functools
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
from packaging import version
|
||||
|
||||
import requests
|
||||
|
||||
from esphome import core
|
||||
from esphome import external_files
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.helpers import (
|
||||
|
@ -15,21 +17,26 @@ from esphome.helpers import (
|
|||
cpp_string_escape,
|
||||
)
|
||||
from esphome.const import (
|
||||
__version__,
|
||||
CONF_FAMILY,
|
||||
CONF_FILE,
|
||||
CONF_GLYPHS,
|
||||
CONF_ID,
|
||||
CONF_RAW_DATA_ID,
|
||||
CONF_TYPE,
|
||||
CONF_REFRESH,
|
||||
CONF_SIZE,
|
||||
CONF_PATH,
|
||||
CONF_WEIGHT,
|
||||
CONF_URL,
|
||||
)
|
||||
from esphome.core import (
|
||||
CORE,
|
||||
HexInt,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "font"
|
||||
DEPENDENCIES = ["display"]
|
||||
MULTI_CONF = True
|
||||
|
@ -125,20 +132,10 @@ def validate_truetype_file(value):
|
|||
return cv.file_(value)
|
||||
|
||||
|
||||
def _compute_local_font_dir(name) -> Path:
|
||||
h = hashlib.new("sha256")
|
||||
h.update(name.encode())
|
||||
return Path(CORE.data_dir) / DOMAIN / h.hexdigest()[:8]
|
||||
|
||||
|
||||
def _compute_gfonts_local_path(value) -> Path:
|
||||
name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1"
|
||||
return _compute_local_font_dir(name) / "font.ttf"
|
||||
|
||||
|
||||
TYPE_LOCAL = "local"
|
||||
TYPE_LOCAL_BITMAP = "local_bitmap"
|
||||
TYPE_GFONTS = "gfonts"
|
||||
TYPE_WEB = "web"
|
||||
LOCAL_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PATH): validate_truetype_file,
|
||||
|
@ -169,21 +166,64 @@ def validate_weight_name(value):
|
|||
return FONT_WEIGHTS[cv.one_of(*FONT_WEIGHTS, lower=True, space="-")(value)]
|
||||
|
||||
|
||||
def download_gfonts(value):
|
||||
def _compute_local_font_path(value: dict) -> Path:
|
||||
url = value[CONF_URL]
|
||||
h = hashlib.new("sha256")
|
||||
h.update(url.encode())
|
||||
key = h.hexdigest()[:8]
|
||||
base_dir = external_files.compute_local_file_dir(DOMAIN)
|
||||
_LOGGER.debug("_compute_local_font_path: base_dir=%s", base_dir / key)
|
||||
return base_dir / key
|
||||
|
||||
|
||||
def get_font_path(value, type) -> Path:
|
||||
if type == TYPE_GFONTS:
|
||||
name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1"
|
||||
return external_files.compute_local_file_dir(DOMAIN) / f"{name}.ttf"
|
||||
if type == TYPE_WEB:
|
||||
return _compute_local_font_path(value) / "font.ttf"
|
||||
return None
|
||||
|
||||
|
||||
def download_content(url: str, path: Path) -> None:
|
||||
if not external_files.has_remote_file_changed(url, path):
|
||||
_LOGGER.debug("Remote file has not changed %s", url)
|
||||
return
|
||||
|
||||
_LOGGER.debug(
|
||||
"Remote file has changed, downloading from %s to %s",
|
||||
url,
|
||||
path,
|
||||
)
|
||||
|
||||
try:
|
||||
req = requests.get(
|
||||
url,
|
||||
timeout=external_files.NETWORK_TIMEOUT,
|
||||
headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"},
|
||||
)
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise cv.Invalid(f"Could not download from {url}: {e}")
|
||||
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(req.content)
|
||||
|
||||
|
||||
def download_gfont(value):
|
||||
name = (
|
||||
f"{value[CONF_FAMILY]}:ital,wght@{int(value[CONF_ITALIC])},{value[CONF_WEIGHT]}"
|
||||
)
|
||||
url = f"https://fonts.googleapis.com/css2?family={name}"
|
||||
path = get_font_path(value, TYPE_GFONTS)
|
||||
_LOGGER.debug("download_gfont: path=%s", path)
|
||||
|
||||
path = _compute_gfonts_local_path(value)
|
||||
if path.is_file():
|
||||
return value
|
||||
try:
|
||||
req = requests.get(url, timeout=30)
|
||||
req = requests.get(url, timeout=external_files.NETWORK_TIMEOUT)
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise cv.Invalid(
|
||||
f"Could not download font for {name}, please check the fonts exists "
|
||||
f"Could not download font at {url}, please check the fonts exists "
|
||||
f"at google fonts ({e})"
|
||||
)
|
||||
match = re.search(r"src:\s+url\((.+)\)\s+format\('truetype'\);", req.text)
|
||||
|
@ -194,26 +234,48 @@ def download_gfonts(value):
|
|||
)
|
||||
|
||||
ttf_url = match.group(1)
|
||||
try:
|
||||
req = requests.get(ttf_url, timeout=30)
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise cv.Invalid(f"Could not download ttf file for {name} ({ttf_url}): {e}")
|
||||
_LOGGER.debug("download_gfont: ttf_url=%s", ttf_url)
|
||||
|
||||
path.parent.mkdir(exist_ok=True, parents=True)
|
||||
path.write_bytes(req.content)
|
||||
download_content(ttf_url, path)
|
||||
return value
|
||||
|
||||
|
||||
GFONTS_SCHEMA = cv.All(
|
||||
def download_web_font(value):
|
||||
url = value[CONF_URL]
|
||||
path = get_font_path(value, TYPE_WEB)
|
||||
|
||||
download_content(url, path)
|
||||
_LOGGER.debug("download_web_font: path=%s", path)
|
||||
return value
|
||||
|
||||
|
||||
EXTERNAL_FONT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_FAMILY): cv.string_strict,
|
||||
cv.Optional(CONF_WEIGHT, default="regular"): cv.Any(
|
||||
cv.int_, validate_weight_name
|
||||
),
|
||||
cv.Optional(CONF_ITALIC, default=False): cv.boolean,
|
||||
},
|
||||
download_gfonts,
|
||||
cv.Optional(CONF_REFRESH, default="1d"): cv.All(cv.string, cv.source_refresh),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
GFONTS_SCHEMA = cv.All(
|
||||
EXTERNAL_FONT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_FAMILY): cv.string_strict,
|
||||
}
|
||||
),
|
||||
download_gfont,
|
||||
)
|
||||
|
||||
WEB_FONT_SCHEMA = cv.All(
|
||||
EXTERNAL_FONT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_URL): cv.string_strict,
|
||||
}
|
||||
),
|
||||
download_web_font,
|
||||
)
|
||||
|
||||
|
||||
|
@ -233,6 +295,14 @@ def validate_file_shorthand(value):
|
|||
data[CONF_WEIGHT] = weight[1:]
|
||||
return FILE_SCHEMA(data)
|
||||
|
||||
if value.startswith("http://") or value.startswith("https://"):
|
||||
return FILE_SCHEMA(
|
||||
{
|
||||
CONF_TYPE: TYPE_WEB,
|
||||
CONF_URL: value,
|
||||
}
|
||||
)
|
||||
|
||||
if value.endswith(".pcf") or value.endswith(".bdf"):
|
||||
return FILE_SCHEMA(
|
||||
{
|
||||
|
@ -254,6 +324,7 @@ TYPED_FILE_SCHEMA = cv.typed_schema(
|
|||
TYPE_LOCAL: LOCAL_SCHEMA,
|
||||
TYPE_GFONTS: GFONTS_SCHEMA,
|
||||
TYPE_LOCAL_BITMAP: LOCAL_BITMAP_SCHEMA,
|
||||
TYPE_WEB: WEB_FONT_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -264,7 +335,7 @@ def _file_schema(value):
|
|||
return TYPED_FILE_SCHEMA(value)
|
||||
|
||||
|
||||
FILE_SCHEMA = cv.Schema(_file_schema)
|
||||
FILE_SCHEMA = cv.All(_file_schema)
|
||||
|
||||
DEFAULT_GLYPHS = (
|
||||
' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
|
||||
|
@ -288,7 +359,7 @@ FONT_SCHEMA = cv.Schema(
|
|||
),
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(GlyphData),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA, merge_glyphs)
|
||||
|
@ -343,8 +414,8 @@ class EFont:
|
|||
elif ftype == TYPE_LOCAL:
|
||||
path = CORE.relative_config_path(file[CONF_PATH])
|
||||
font = load_ttf_font(path, size)
|
||||
elif ftype == TYPE_GFONTS:
|
||||
path = _compute_gfonts_local_path(file)
|
||||
elif ftype in (TYPE_GFONTS, TYPE_WEB):
|
||||
path = get_font_path(file, ftype)
|
||||
font = load_ttf_font(path, size)
|
||||
else:
|
||||
raise cv.Invalid(f"Could not load font: unknown type: {ftype}")
|
||||
|
@ -361,9 +432,9 @@ def convert_bitmap_to_pillow_font(filepath):
|
|||
BdfFontFile,
|
||||
)
|
||||
|
||||
local_bitmap_font_file = _compute_local_font_dir(filepath) / os.path.basename(
|
||||
filepath
|
||||
)
|
||||
local_bitmap_font_file = external_files.compute_local_file_dir(
|
||||
DOMAIN,
|
||||
) / os.path.basename(filepath)
|
||||
|
||||
copy_file_if_changed(filepath, local_bitmap_font_file)
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ class Glyph {
|
|||
|
||||
void scan_area(int *x1, int *y1, int *width, int *height) const;
|
||||
|
||||
const GlyphData *get_glyph_data() const { return this->glyph_data_; }
|
||||
|
||||
protected:
|
||||
friend Font;
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ class FT5x06Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
|
|||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
}
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_height();
|
||||
this->y_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
}
|
||||
esph_log_config(TAG, "FT5x06 Touchscreen setup complete");
|
||||
|
|
|
@ -1,36 +1,87 @@
|
|||
#ifdef USE_HOST
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include "preferences.h"
|
||||
#include <cstring>
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace host {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static const char *const TAG = "host.preferences";
|
||||
|
||||
class HostPreferences : public ESPPreferences {
|
||||
public:
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { return {}; }
|
||||
void HostPreferences::setup_() {
|
||||
if (this->setup_complete_)
|
||||
return;
|
||||
this->filename_.append(getenv("HOME"));
|
||||
this->filename_.append("/.esphome");
|
||||
this->filename_.append("/prefs");
|
||||
fs::create_directories(this->filename_);
|
||||
this->filename_.append("/");
|
||||
this->filename_.append(App.get_name());
|
||||
this->filename_.append(".prefs");
|
||||
FILE *fp = fopen(this->filename_.c_str(), "rb");
|
||||
if (fp != nullptr) {
|
||||
while (!feof((fp))) {
|
||||
uint32_t key;
|
||||
uint8_t len;
|
||||
if (fread(&key, sizeof(key), 1, fp) != 1)
|
||||
break;
|
||||
if (fread(&len, sizeof(len), 1, fp) != 1)
|
||||
break;
|
||||
uint8_t data[len];
|
||||
if (fread(data, sizeof(uint8_t), len, fp) != len)
|
||||
break;
|
||||
std::vector vec(data, data + len);
|
||||
this->data[key] = vec;
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
this->setup_complete_ = true;
|
||||
}
|
||||
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type) override { return {}; }
|
||||
bool HostPreferences::sync() {
|
||||
this->setup_();
|
||||
FILE *fp = fopen(this->filename_.c_str(), "wb");
|
||||
std::map<uint32_t, std::vector<uint8_t>>::iterator it;
|
||||
|
||||
bool sync() override { return true; }
|
||||
bool reset() override { return true; }
|
||||
for (it = this->data.begin(); it != this->data.end(); ++it) {
|
||||
fwrite(&it->first, sizeof(uint32_t), 1, fp);
|
||||
uint8_t len = it->second.size();
|
||||
fwrite(&len, sizeof(len), 1, fp);
|
||||
fwrite(it->second.data(), sizeof(uint8_t), it->second.size(), fp);
|
||||
}
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostPreferences::reset() {
|
||||
host_preferences->data.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
ESPPreferenceObject HostPreferences::make_preference(size_t length, uint32_t type, bool in_flash) {
|
||||
auto backend = new HostPreferenceBackend(type);
|
||||
return ESPPreferenceObject(backend);
|
||||
};
|
||||
|
||||
void setup_preferences() {
|
||||
auto *pref = new HostPreferences(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
host_preferences = pref;
|
||||
global_preferences = pref;
|
||||
}
|
||||
|
||||
bool HostPreferenceBackend::save(const uint8_t *data, size_t len) {
|
||||
return host_preferences->save(this->key_, data, len);
|
||||
}
|
||||
|
||||
bool HostPreferenceBackend::load(uint8_t *data, size_t len) { return host_preferences->load(this->key_, data, len); }
|
||||
|
||||
HostPreferences *host_preferences;
|
||||
} // namespace host
|
||||
|
||||
ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
||||
|
|
|
@ -2,10 +2,63 @@
|
|||
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include "esphome/core/preferences.h"
|
||||
#include <map>
|
||||
|
||||
namespace esphome {
|
||||
namespace host {
|
||||
|
||||
class HostPreferenceBackend : public ESPPreferenceBackend {
|
||||
public:
|
||||
explicit HostPreferenceBackend(uint32_t key) { this->key_ = key; }
|
||||
|
||||
bool save(const uint8_t *data, size_t len) override;
|
||||
bool load(uint8_t *data, size_t len) override;
|
||||
|
||||
protected:
|
||||
uint32_t key_{};
|
||||
};
|
||||
|
||||
class HostPreferences : public ESPPreferences {
|
||||
public:
|
||||
bool sync() override;
|
||||
bool reset() override;
|
||||
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override;
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
|
||||
return make_preference(length, type, false);
|
||||
}
|
||||
|
||||
bool save(uint32_t key, const uint8_t *data, size_t len) {
|
||||
if (len > 255)
|
||||
return false;
|
||||
this->setup_();
|
||||
std::vector vec(data, data + len);
|
||||
this->data[key] = vec;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load(uint32_t key, uint8_t *data, size_t len) {
|
||||
if (len > 255)
|
||||
return false;
|
||||
this->setup_();
|
||||
if (this->data.count(key) == 0)
|
||||
return false;
|
||||
auto vec = this->data[key];
|
||||
if (vec.size() != len)
|
||||
return false;
|
||||
memcpy(data, vec.data(), len);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
void setup_();
|
||||
bool setup_complete_{};
|
||||
std::string filename_{};
|
||||
std::map<uint32_t, std::vector<uint8_t>> data{};
|
||||
};
|
||||
void setup_preferences();
|
||||
extern HostPreferences *host_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace host
|
||||
} // namespace esphome
|
||||
|
|
1
esphome/components/htu31d/__init__.py
Normal file
1
esphome/components/htu31d/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@betterengineering"]
|
269
esphome/components/htu31d/htu31d.cpp
Normal file
269
esphome/components/htu31d/htu31d.cpp
Normal file
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* This file contains source code derived from Adafruit_HTU31D which is under
|
||||
* the BSD license:
|
||||
* Written by Limor Fried/Ladyada for Adafruit Industries.
|
||||
* BSD license, all text above must be included in any redistribution.
|
||||
*
|
||||
* Modifications made by Mark Spicer.
|
||||
*/
|
||||
|
||||
#include "htu31d.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace htu31d {
|
||||
|
||||
/** Logging prefix */
|
||||
static const char *const TAG = "htu31d";
|
||||
|
||||
/** Default I2C address for the HTU31D. */
|
||||
static const uint8_t HTU31D_DEFAULT_I2CADDR = 0x40;
|
||||
|
||||
/** Read temperature and humidity. */
|
||||
static const uint8_t HTU31D_READTEMPHUM = 0x00;
|
||||
|
||||
/** Start a conversion! */
|
||||
static const uint8_t HTU31D_CONVERSION = 0x40;
|
||||
|
||||
/** Read serial number command. */
|
||||
static const uint8_t HTU31D_READSERIAL = 0x0A;
|
||||
|
||||
/** Enable heater */
|
||||
static const uint8_t HTU31D_HEATERON = 0x04;
|
||||
|
||||
/** Disable heater */
|
||||
static const uint8_t HTU31D_HEATEROFF = 0x02;
|
||||
|
||||
/** Reset command. */
|
||||
static const uint8_t HTU31D_RESET = 0x1E;
|
||||
|
||||
/** Diagnostics command. */
|
||||
static const uint8_t HTU31D_DIAGNOSTICS = 0x08;
|
||||
|
||||
/**
|
||||
* Computes a CRC result for the provided input.
|
||||
*
|
||||
* @returns the computed CRC result for the provided input
|
||||
*/
|
||||
uint8_t compute_crc(uint32_t value) {
|
||||
uint32_t polynom = 0x98800000; // x^8 + x^5 + x^4 + 1
|
||||
uint32_t msb = 0x80000000;
|
||||
uint32_t mask = 0xFF800000;
|
||||
uint32_t threshold = 0x00000080;
|
||||
uint32_t result = value;
|
||||
|
||||
while (msb != threshold) {
|
||||
// Check if msb of current value is 1 and apply XOR mask
|
||||
if (result & msb)
|
||||
result = ((result ^ polynom) & mask) | (result & ~mask);
|
||||
|
||||
// Shift by one
|
||||
msb >>= 1;
|
||||
mask >>= 1;
|
||||
polynom >>= 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the sensor and ensures that the devices serial number can be read over
|
||||
* I2C.
|
||||
*/
|
||||
void HTU31DComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up esphome/components/htu31d HTU31D...");
|
||||
|
||||
if (!this->reset_()) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->read_serial_num_() == 0) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once every update interval (user configured, defaults to 60s) and sets
|
||||
* the current temperature and humidity.
|
||||
*/
|
||||
void HTU31DComponent::update() {
|
||||
ESP_LOGD(TAG, "Checking temperature and humidty values");
|
||||
|
||||
// Trigger a conversion. From the spec sheet: The conversion command triggers
|
||||
// a single temperature and humidity conversion.
|
||||
if (this->write_register(HTU31D_CONVERSION, nullptr, 0) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "Received errror writing conversion register");
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait conversion time.
|
||||
this->set_timeout(20, [this]() {
|
||||
uint8_t thdata[6];
|
||||
if (this->read_register(HTU31D_READTEMPHUM, thdata, 6) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "Error reading temperature/humidty register");
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate temperature value.
|
||||
uint16_t raw_temp = encode_uint16(thdata[0], thdata[1]);
|
||||
|
||||
uint8_t crc = compute_crc((uint32_t) raw_temp << 8);
|
||||
if (crc != thdata[2]) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "Error validating temperature CRC");
|
||||
return;
|
||||
}
|
||||
|
||||
float temperature = raw_temp;
|
||||
temperature /= 65535.0f;
|
||||
temperature *= 165;
|
||||
temperature -= 40;
|
||||
|
||||
if (this->temperature_ != nullptr) {
|
||||
this->temperature_->publish_state(temperature);
|
||||
}
|
||||
|
||||
// Calculate humidty value.
|
||||
uint16_t raw_hum = encode_uint16(thdata[3], thdata[4]);
|
||||
|
||||
crc = compute_crc((uint32_t) raw_hum << 8);
|
||||
if (crc != thdata[5]) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "Error validating humidty CRC");
|
||||
return;
|
||||
}
|
||||
|
||||
float humidity = raw_hum;
|
||||
humidity /= 65535.0f;
|
||||
humidity *= 100;
|
||||
|
||||
if (this->humidity_ != nullptr) {
|
||||
this->humidity_->publish_state(humidity);
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity);
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the current compoenent config.
|
||||
*/
|
||||
void HTU31DComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "HTU31D:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with HTU31D failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a 'reset' request to the HTU31D, followed by a 15ms delay.
|
||||
*
|
||||
* @returns True if was able to write the command successfully
|
||||
*/
|
||||
bool HTU31DComponent::reset_() {
|
||||
if (this->write_register(HTU31D_RESET, nullptr, 0) != i2c::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
delay(15);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the serial number from the device and checks the CRC.
|
||||
*
|
||||
* @returns the 24bit serial number from the device
|
||||
*/
|
||||
uint32_t HTU31DComponent::read_serial_num_() {
|
||||
uint8_t reply[4];
|
||||
uint32_t serial = 0;
|
||||
uint8_t padding = 0;
|
||||
|
||||
// Verify we can read the device serial.
|
||||
if (this->read_register(HTU31D_READSERIAL, reply, 4) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Error reading device serial");
|
||||
return 0;
|
||||
}
|
||||
|
||||
serial = encode_uint32(reply[0], reply[1], reply[2], padding);
|
||||
|
||||
uint8_t crc = compute_crc(serial);
|
||||
if (crc != reply[3]) {
|
||||
ESP_LOGE(TAG, "Error validating serial CRC");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Found serial: 0x%X", serial);
|
||||
|
||||
return serial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the diagnostics register to determine if the heater is currently
|
||||
* enabled.
|
||||
*
|
||||
* @returns True if the heater is currently enabled, False otherwise
|
||||
*/
|
||||
bool HTU31DComponent::is_heater_enabled() {
|
||||
uint8_t reply[1];
|
||||
uint8_t heater_enabled_position = 0;
|
||||
uint8_t mask = 1 << heater_enabled_position;
|
||||
uint8_t diagnostics = 0;
|
||||
|
||||
if (this->read_register(HTU31D_DIAGNOSTICS, reply, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Error reading device serial");
|
||||
return false;
|
||||
}
|
||||
|
||||
diagnostics = reply[0];
|
||||
return (diagnostics & mask) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the heater state on or off.
|
||||
*
|
||||
* @param desired True for on, and False for off.
|
||||
*/
|
||||
void HTU31DComponent::set_heater_state(bool desired) {
|
||||
bool current = this->is_heater_enabled();
|
||||
|
||||
// If the current state matches the desired state, there is nothing to do.
|
||||
if (current == desired) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update heater state.
|
||||
esphome::i2c::ErrorCode err;
|
||||
if (desired) {
|
||||
err = this->write_register(HTU31D_HEATERON, nullptr, 0);
|
||||
} else {
|
||||
err = this->write_register(HTU31D_HEATEROFF, nullptr, 0);
|
||||
}
|
||||
|
||||
// Record any error.
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "Received error updating heater state");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the startup priority for this component.
|
||||
*
|
||||
* @returns The startup priority
|
||||
*/
|
||||
float HTU31DComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
} // namespace htu31d
|
||||
} // namespace esphome
|
33
esphome/components/htu31d/htu31d.h
Normal file
33
esphome/components/htu31d/htu31d.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace htu31d {
|
||||
|
||||
class HTU31DComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override; /// Setup (reset) the sensor and check connection.
|
||||
void update() override; /// Update the sensor values (temperature+humidity).
|
||||
void dump_config() override; /// Dumps the configuration values.
|
||||
|
||||
void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; }
|
||||
void set_humidity(sensor::Sensor *humidity) { this->humidity_ = humidity; }
|
||||
|
||||
void set_heater_state(bool desired);
|
||||
bool is_heater_enabled();
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
bool reset_();
|
||||
uint32_t read_serial_num_();
|
||||
|
||||
sensor::Sensor *temperature_{nullptr};
|
||||
sensor::Sensor *humidity_{nullptr};
|
||||
};
|
||||
} // namespace htu31d
|
||||
} // namespace esphome
|
56
esphome/components/htu31d/sensor.py
Normal file
56
esphome/components/htu31d/sensor.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
htu31d_ns = cg.esphome_ns.namespace("htu31d")
|
||||
HTU31DComponent = htu31d_ns.class_(
|
||||
"HTU31DComponent", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HTU31DComponent),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x40))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity(sens))
|
|
@ -6,6 +6,7 @@ DEPENDENCIES = ["uart"]
|
|||
|
||||
hydreon_rgxx_ns = cg.esphome_ns.namespace("hydreon_rgxx")
|
||||
RGModel = hydreon_rgxx_ns.enum("RGModel")
|
||||
RG15Resolution = hydreon_rgxx_ns.enum("RG15Resolution")
|
||||
HydreonRGxxComponent = hydreon_rgxx_ns.class_(
|
||||
"HydreonRGxxComponent", cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
|
|
|
@ -22,6 +22,11 @@ void HydreonRGxxComponent::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " Disable Led: %s", TRUEFALSE(this->disable_led_));
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Model: RG15");
|
||||
if (this->resolution_ == FORCE_HIGH) {
|
||||
ESP_LOGCONFIG(TAG, " Resolution: high");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Resolution: low");
|
||||
}
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
|
@ -195,7 +200,11 @@ void HydreonRGxxComponent::process_line_() {
|
|||
ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
|
||||
|
||||
if (this->model_ == RG15) {
|
||||
if (this->resolution_ == FORCE_HIGH) {
|
||||
this->write_str("P\nH\nM\n"); // set sensor to (P)polling mode, (H)high res mode, (M)metric mode
|
||||
} else {
|
||||
this->write_str("P\nL\nM\n"); // set sensor to (P)polling mode, (L)low res mode, (M)metric mode
|
||||
}
|
||||
}
|
||||
|
||||
if (this->model_ == RG9) {
|
||||
|
|
|
@ -16,6 +16,11 @@ enum RGModel {
|
|||
RG15 = 2,
|
||||
};
|
||||
|
||||
enum RG15Resolution {
|
||||
FORCE_LOW = 1,
|
||||
FORCE_HIGH = 2,
|
||||
};
|
||||
|
||||
#ifdef HYDREON_RGXX_NUM_SENSORS
|
||||
static const uint8_t NUM_SENSORS = HYDREON_RGXX_NUM_SENSORS;
|
||||
#else
|
||||
|
@ -37,6 +42,7 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice {
|
|||
void set_em_sat_sensor(binary_sensor::BinarySensor *sensor) { this->em_sat_sensor_ = sensor; }
|
||||
#endif
|
||||
void set_model(RGModel model) { model_ = model; }
|
||||
void set_resolution(RG15Resolution resolution) { resolution_ = resolution; }
|
||||
void set_request_temperature(bool b) { request_temperature_ = b; }
|
||||
|
||||
/// Schedule data readings.
|
||||
|
@ -68,7 +74,10 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice {
|
|||
int16_t boot_count_ = 0;
|
||||
int16_t no_response_count_ = 0;
|
||||
std::string buffer_;
|
||||
|
||||
RGModel model_ = RG9;
|
||||
RG15Resolution resolution_ = FORCE_HIGH;
|
||||
|
||||
int sw_version_ = 0;
|
||||
bool too_cold_ = false;
|
||||
bool lens_bad_ = false;
|
||||
|
|
|
@ -5,6 +5,7 @@ from esphome.const import (
|
|||
CONF_ID,
|
||||
CONF_MODEL,
|
||||
CONF_MOISTURE,
|
||||
CONF_RESOLUTION,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_PRECIPITATION_INTENSITY,
|
||||
DEVICE_CLASS_PRECIPITATION,
|
||||
|
@ -14,7 +15,7 @@ from esphome.const import (
|
|||
ICON_THERMOMETER,
|
||||
)
|
||||
|
||||
from . import RGModel, HydreonRGxxComponent
|
||||
from . import RGModel, RG15Resolution, HydreonRGxxComponent
|
||||
|
||||
UNIT_INTENSITY = "intensity"
|
||||
UNIT_MILLIMETERS = "mm"
|
||||
|
@ -37,11 +38,18 @@ RG_MODELS = {
|
|||
# 1.100 - https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2021.03.11-rg-9_instructions.pdf
|
||||
# 1.200 - https://rainsensors.com/wp-content/uploads/sites/3/2022/03/2022.02.17-rev-1.200-rg-9_instructions.pdf
|
||||
}
|
||||
SUPPORTED_SENSORS = {
|
||||
|
||||
RG15_RESOLUTION = {
|
||||
"low": RG15Resolution.FORCE_LOW,
|
||||
"high": RG15Resolution.FORCE_HIGH,
|
||||
}
|
||||
|
||||
SUPPORTED_OPTIONS = {
|
||||
CONF_ACC: ["RG_15"],
|
||||
CONF_EVENT_ACC: ["RG_15"],
|
||||
CONF_TOTAL_ACC: ["RG_15"],
|
||||
CONF_R_INT: ["RG_15"],
|
||||
CONF_RESOLUTION: ["RG_15"],
|
||||
CONF_MOISTURE: ["RG_9"],
|
||||
CONF_TEMPERATURE: ["RG_9"],
|
||||
CONF_DISABLE_LED: ["RG_9"],
|
||||
|
@ -57,7 +65,7 @@ PROTOCOL_NAMES = {
|
|||
|
||||
|
||||
def _validate(config):
|
||||
for conf, models in SUPPORTED_SENSORS.items():
|
||||
for conf, models in SUPPORTED_OPTIONS.items():
|
||||
if conf in config:
|
||||
if config[CONF_MODEL] not in models:
|
||||
raise cv.Invalid(
|
||||
|
@ -75,6 +83,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
upper=True,
|
||||
space="_",
|
||||
),
|
||||
cv.Optional(CONF_RESOLUTION): cv.enum(RG15_RESOLUTION, upper=False),
|
||||
cv.Optional(CONF_ACC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETERS,
|
||||
accuracy_decimals=2,
|
||||
|
@ -139,6 +148,9 @@ async def to_code(config):
|
|||
cg.add(var.set_sensor(sens, i))
|
||||
|
||||
cg.add(var.set_model(config[CONF_MODEL]))
|
||||
if CONF_RESOLUTION in config:
|
||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
||||
|
||||
cg.add(var.set_request_temperature(CONF_TEMPERATURE in config))
|
||||
|
||||
if CONF_DISABLE_LED in config:
|
||||
|
|
|
@ -12,12 +12,12 @@ static const char *const TAG = "audio";
|
|||
void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
||||
if (call.get_media_url().has_value()) {
|
||||
this->current_url_ = call.get_media_url();
|
||||
|
||||
if (this->state == media_player::MEDIA_PLAYER_STATE_PLAYING && this->audio_ != nullptr) {
|
||||
if (this->i2s_state_ != I2S_STATE_STOPPED && this->audio_ != nullptr) {
|
||||
if (this->audio_->isRunning()) {
|
||||
this->audio_->stopSong();
|
||||
}
|
||||
this->audio_->connecttohost(this->current_url_.value().c_str());
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
} else {
|
||||
this->start();
|
||||
}
|
||||
|
|
|
@ -55,10 +55,10 @@ void INA226Component::setup() {
|
|||
config.avg_samples = this->adc_avg_samples_;
|
||||
|
||||
// Bus Voltage Conversion Time VBUSCT Bit Settings [8:6] (100 -> 1.1ms, 111 -> 8.244 ms)
|
||||
config.bus_voltage_conversion_time = this->adc_time_;
|
||||
config.bus_voltage_conversion_time = this->adc_time_voltage_;
|
||||
|
||||
// Shunt Voltage Conversion Time VSHCT Bit Settings [5:3] (100 -> 1.1ms, 111 -> 8.244 ms)
|
||||
config.shunt_voltage_conversion_time = this->adc_time_;
|
||||
config.shunt_voltage_conversion_time = this->adc_time_current_;
|
||||
|
||||
// Mode Settings [2:0] Combinations (111 -> Shunt and Bus, Continuous)
|
||||
config.mode = 0b111;
|
||||
|
@ -93,7 +93,8 @@ void INA226Component::dump_config() {
|
|||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
ESP_LOGCONFIG(TAG, " ADC Conversion Time: %d", INA226_ADC_TIMES[this->adc_time_ & 0b111]);
|
||||
ESP_LOGCONFIG(TAG, " ADC Conversion Time Bus Voltage: %d", INA226_ADC_TIMES[this->adc_time_voltage_ & 0b111]);
|
||||
ESP_LOGCONFIG(TAG, " ADC Conversion Time Shunt Voltage: %d", INA226_ADC_TIMES[this->adc_time_current_ & 0b111]);
|
||||
ESP_LOGCONFIG(TAG, " ADC Averaging Samples: %d", INA226_ADC_AVG_SAMPLES[this->adc_avg_samples_ & 0b111]);
|
||||
|
||||
LOG_SENSOR(" ", "Bus Voltage", this->bus_voltage_sensor_);
|
||||
|
|
|
@ -50,7 +50,8 @@ class INA226Component : public PollingComponent, public i2c::I2CDevice {
|
|||
|
||||
void set_shunt_resistance_ohm(float shunt_resistance_ohm) { shunt_resistance_ohm_ = shunt_resistance_ohm; }
|
||||
void set_max_current_a(float max_current_a) { max_current_a_ = max_current_a; }
|
||||
void set_adc_time(AdcTime time) { adc_time_ = time; }
|
||||
void set_adc_time_voltage(AdcTime time) { adc_time_voltage_ = time; }
|
||||
void set_adc_time_current(AdcTime time) { adc_time_current_ = time; }
|
||||
void set_adc_avg_samples(AdcAvgSamples samples) { adc_avg_samples_ = samples; }
|
||||
|
||||
void set_bus_voltage_sensor(sensor::Sensor *bus_voltage_sensor) { bus_voltage_sensor_ = bus_voltage_sensor; }
|
||||
|
@ -61,7 +62,8 @@ class INA226Component : public PollingComponent, public i2c::I2CDevice {
|
|||
protected:
|
||||
float shunt_resistance_ohm_;
|
||||
float max_current_a_;
|
||||
AdcTime adc_time_{AdcTime::ADC_TIME_1100US};
|
||||
AdcTime adc_time_voltage_{AdcTime::ADC_TIME_1100US};
|
||||
AdcTime adc_time_current_{AdcTime::ADC_TIME_1100US};
|
||||
AdcAvgSamples adc_avg_samples_{AdcAvgSamples::ADC_AVG_SAMPLES_4};
|
||||
uint32_t calibration_lsb_;
|
||||
sensor::Sensor *bus_voltage_sensor_{nullptr};
|
||||
|
|
|
@ -16,6 +16,7 @@ from esphome.const import (
|
|||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_WATT,
|
||||
CONF_VOLTAGE,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
@ -92,7 +93,15 @@ CONFIG_SCHEMA = (
|
|||
cv.Optional(CONF_MAX_CURRENT, default=3.2): cv.All(
|
||||
cv.current, cv.Range(min=0.0)
|
||||
),
|
||||
cv.Optional(CONF_ADC_TIME, default="1100 us"): validate_adc_time,
|
||||
cv.Optional(CONF_ADC_TIME, default="1100 us"): cv.Any(
|
||||
validate_adc_time,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_VOLTAGE): validate_adc_time,
|
||||
cv.Required(CONF_CURRENT): validate_adc_time,
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_ADC_AVERAGING, default=4): cv.enum(
|
||||
ADC_AVG_SAMPLES, int=True
|
||||
),
|
||||
|
@ -110,7 +119,15 @@ async def to_code(config):
|
|||
|
||||
cg.add(var.set_shunt_resistance_ohm(config[CONF_SHUNT_RESISTANCE]))
|
||||
cg.add(var.set_max_current_a(config[CONF_MAX_CURRENT]))
|
||||
cg.add(var.set_adc_time(config[CONF_ADC_TIME]))
|
||||
|
||||
adc_time_config = config[CONF_ADC_TIME]
|
||||
if isinstance(adc_time_config, dict):
|
||||
cg.add(var.set_adc_time_voltage(adc_time_config[CONF_VOLTAGE]))
|
||||
cg.add(var.set_adc_time_current(adc_time_config[CONF_CURRENT]))
|
||||
else:
|
||||
cg.add(var.set_adc_time_voltage(adc_time_config))
|
||||
cg.add(var.set_adc_time_current(adc_time_config))
|
||||
|
||||
cg.add(var.set_adc_avg_samples(config[CONF_ADC_AVERAGING]))
|
||||
|
||||
if CONF_BUS_VOLTAGE in config:
|
||||
|
|
1
esphome/components/jsn_sr04t/__init__.py
Normal file
1
esphome/components/jsn_sr04t/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@Mafus1"]
|
58
esphome/components/jsn_sr04t/jsn_sr04t.cpp
Normal file
58
esphome/components/jsn_sr04t/jsn_sr04t.cpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
#include "jsn_sr04t.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
// Very basic support for JSN_SR04T V3.0 distance sensor in mode 2
|
||||
|
||||
namespace esphome {
|
||||
namespace jsn_sr04t {
|
||||
|
||||
static const char *const TAG = "jsn_sr04t.sensor";
|
||||
|
||||
void Jsnsr04tComponent::update() {
|
||||
this->write_byte(0x55);
|
||||
ESP_LOGV(TAG, "Request read out from sensor");
|
||||
}
|
||||
|
||||
void Jsnsr04tComponent::loop() {
|
||||
while (this->available() > 0) {
|
||||
uint8_t data;
|
||||
this->read_byte(&data);
|
||||
|
||||
ESP_LOGV(TAG, "Read byte from sensor: %x", data);
|
||||
|
||||
if (this->buffer_.empty() && data != 0xFF)
|
||||
continue;
|
||||
|
||||
this->buffer_.push_back(data);
|
||||
if (this->buffer_.size() == 4)
|
||||
this->check_buffer_();
|
||||
}
|
||||
}
|
||||
|
||||
void Jsnsr04tComponent::check_buffer_() {
|
||||
uint8_t checksum = this->buffer_[0] + this->buffer_[1] + this->buffer_[2];
|
||||
if (this->buffer_[3] == checksum) {
|
||||
uint16_t distance = encode_uint16(this->buffer_[1], this->buffer_[2]);
|
||||
if (distance > 250) {
|
||||
float meters = distance / 1000.0f;
|
||||
ESP_LOGV(TAG, "Distance from sensor: %" PRIu32 "mm, %.3fm", distance, meters);
|
||||
this->publish_state(meters);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]);
|
||||
}
|
||||
this->buffer_.clear();
|
||||
}
|
||||
|
||||
void Jsnsr04tComponent::dump_config() {
|
||||
LOG_SENSOR("", "JST_SR04T Sensor", this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
} // namespace jsn_sr04t
|
||||
} // namespace esphome
|
28
esphome/components/jsn_sr04t/jsn_sr04t.h
Normal file
28
esphome/components/jsn_sr04t/jsn_sr04t.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace jsn_sr04t {
|
||||
|
||||
class Jsnsr04tComponent : public sensor::Sensor, public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
// Nothing really public.
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
void update() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void check_buffer_();
|
||||
|
||||
std::vector<uint8_t> buffer_;
|
||||
};
|
||||
|
||||
} // namespace jsn_sr04t
|
||||
} // namespace esphome
|
44
esphome/components/jsn_sr04t/sensor.py
Normal file
44
esphome/components/jsn_sr04t/sensor.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.const import (
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_METER,
|
||||
ICON_ARROW_EXPAND_VERTICAL,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@Mafus1"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
jsn_sr04t_ns = cg.esphome_ns.namespace("jsn_sr04t")
|
||||
Jsnsr04tComponent = jsn_sr04t_ns.class_(
|
||||
"Jsnsr04tComponent", sensor.Sensor, cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
Jsnsr04tComponent,
|
||||
unit_of_measurement=UNIT_METER,
|
||||
icon=ICON_ARROW_EXPAND_VERTICAL,
|
||||
accuracy_decimals=3,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"jsn_sr04t",
|
||||
baud_rate=9600,
|
||||
require_tx=True,
|
||||
require_rx=True,
|
||||
data_bits=8,
|
||||
parity=None,
|
||||
stop_bits=1,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
0
esphome/components/kamstrup_kmp/__init__.py
Normal file
0
esphome/components/kamstrup_kmp/__init__.py
Normal file
301
esphome/components/kamstrup_kmp/kamstrup_kmp.cpp
Normal file
301
esphome/components/kamstrup_kmp/kamstrup_kmp.cpp
Normal file
|
@ -0,0 +1,301 @@
|
|||
#include "kamstrup_kmp.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace kamstrup_kmp {
|
||||
|
||||
static const char *const TAG = "kamstrup_kmp";
|
||||
|
||||
void KamstrupKMPComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "kamstrup_kmp:");
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with Kamstrup meter failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
LOG_SENSOR(" ", "Heat Energy", this->heat_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 1", this->temp1_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 2", this->temp2_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature Difference", this->temp_diff_sensor_);
|
||||
LOG_SENSOR(" ", "Flow", this->flow_sensor_);
|
||||
LOG_SENSOR(" ", "Volume", this->volume_sensor_);
|
||||
|
||||
for (int i = 0; i < this->custom_sensors_.size(); i++) {
|
||||
LOG_SENSOR(" ", "Custom Sensor", this->custom_sensors_[i]);
|
||||
ESP_LOGCONFIG(TAG, " Command: 0x%04X", this->custom_commands_[i]);
|
||||
}
|
||||
|
||||
this->check_uart_settings(1200, 2, uart::UART_CONFIG_PARITY_NONE, 8);
|
||||
}
|
||||
|
||||
float KamstrupKMPComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void KamstrupKMPComponent::update() {
|
||||
if (this->heat_energy_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_HEAT_ENERGY);
|
||||
}
|
||||
|
||||
if (this->power_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_POWER);
|
||||
}
|
||||
|
||||
if (this->temp1_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_TEMP1);
|
||||
}
|
||||
|
||||
if (this->temp2_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_TEMP2);
|
||||
}
|
||||
|
||||
if (this->temp_diff_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_TEMP_DIFF);
|
||||
}
|
||||
|
||||
if (this->flow_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_FLOW);
|
||||
}
|
||||
|
||||
if (this->volume_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_VOLUME);
|
||||
}
|
||||
|
||||
for (uint16_t custom_command : this->custom_commands_) {
|
||||
this->command_queue_.push(custom_command);
|
||||
}
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::loop() {
|
||||
if (!this->command_queue_.empty()) {
|
||||
uint16_t command = this->command_queue_.front();
|
||||
this->send_command_(command);
|
||||
this->command_queue_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::send_command_(uint16_t command) {
|
||||
uint32_t msg_len = 5;
|
||||
uint8_t msg[msg_len];
|
||||
|
||||
msg[0] = 0x3F;
|
||||
msg[1] = 0x10;
|
||||
msg[2] = 0x01;
|
||||
msg[3] = command >> 8;
|
||||
msg[4] = command & 0xFF;
|
||||
|
||||
this->clear_uart_rx_buffer_();
|
||||
this->send_message_(msg, msg_len);
|
||||
this->read_command_(command);
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::send_message_(const uint8_t *msg, int msg_len) {
|
||||
int buffer_len = msg_len + 2;
|
||||
uint8_t buffer[buffer_len];
|
||||
|
||||
// Prepare the basic message and appand CRC
|
||||
for (int i = 0; i < msg_len; i++) {
|
||||
buffer[i] = msg[i];
|
||||
}
|
||||
|
||||
buffer[buffer_len - 2] = 0;
|
||||
buffer[buffer_len - 1] = 0;
|
||||
|
||||
uint16_t crc = crc16_ccitt(buffer, buffer_len);
|
||||
buffer[buffer_len - 2] = crc >> 8;
|
||||
buffer[buffer_len - 1] = crc & 0xFF;
|
||||
|
||||
// Prepare actual TX message
|
||||
uint8_t tx_msg[20];
|
||||
int tx_msg_len = 1;
|
||||
tx_msg[0] = 0x80; // prefix
|
||||
|
||||
for (int i = 0; i < buffer_len; i++) {
|
||||
if (buffer[i] == 0x06 || buffer[i] == 0x0d || buffer[i] == 0x1b || buffer[i] == 0x40 || buffer[i] == 0x80) {
|
||||
tx_msg[tx_msg_len++] = 0x1b;
|
||||
tx_msg[tx_msg_len++] = buffer[i] ^ 0xff;
|
||||
} else {
|
||||
tx_msg[tx_msg_len++] = buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
tx_msg[tx_msg_len++] = 0x0D; // EOM
|
||||
|
||||
this->write_array(tx_msg, tx_msg_len);
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::clear_uart_rx_buffer_() {
|
||||
uint8_t tmp;
|
||||
while (this->available()) {
|
||||
this->read_byte(&tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::read_command_(uint16_t command) {
|
||||
uint8_t buffer[20] = {0};
|
||||
int buffer_len = 0;
|
||||
int data;
|
||||
int timeout = 250; // ms
|
||||
|
||||
// Read the data from the UART
|
||||
while (timeout > 0) {
|
||||
if (this->available()) {
|
||||
data = this->read();
|
||||
if (data > -1) {
|
||||
if (data == 0x40) { // start of message
|
||||
buffer_len = 0;
|
||||
}
|
||||
buffer[buffer_len++] = (uint8_t) data;
|
||||
if (data == 0x0D) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error while reading from UART");
|
||||
}
|
||||
} else {
|
||||
delay(1);
|
||||
timeout--;
|
||||
}
|
||||
}
|
||||
|
||||
if (timeout == 0 || buffer_len == 0) {
|
||||
ESP_LOGE(TAG, "Request timed out");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate message (prefix and suffix)
|
||||
if (buffer[0] != 0x40) {
|
||||
ESP_LOGE(TAG, "Received invalid message (prefix mismatch received 0x%02X, expected 0x40)", buffer[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer[buffer_len - 1] != 0x0D) {
|
||||
ESP_LOGE(TAG, "Received invalid message (EOM mismatch received 0x%02X, expected 0x0D)", buffer[buffer_len - 1]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode
|
||||
uint8_t msg[20] = {0};
|
||||
int msg_len = 0;
|
||||
for (int i = 1; i < buffer_len - 1; i++) {
|
||||
if (buffer[i] == 0x1B) {
|
||||
msg[msg_len++] = buffer[i + 1] ^ 0xFF;
|
||||
i++;
|
||||
} else {
|
||||
msg[msg_len++] = buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Validate CRC
|
||||
if (crc16_ccitt(msg, msg_len)) {
|
||||
ESP_LOGE(TAG, "Received invalid message (CRC mismatch)");
|
||||
return;
|
||||
}
|
||||
|
||||
// All seems good. Now parse the message
|
||||
this->parse_command_message_(command, msg, msg_len);
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::parse_command_message_(uint16_t command, const uint8_t *msg, int msg_len) {
|
||||
// Validate the message
|
||||
if (msg_len < 8) {
|
||||
ESP_LOGE(TAG, "Received invalid message (message too small)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg[0] != 0x3F || msg[1] != 0x10) {
|
||||
ESP_LOGE(TAG, "Received invalid message (invalid header received 0x%02X%02X, expected 0x3F10)", msg[0], msg[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t recv_command = msg[2] << 8 | msg[3];
|
||||
if (recv_command != command) {
|
||||
ESP_LOGE(TAG, "Received invalid message (invalid unexpected command received 0x%04X, expected 0x%04X)",
|
||||
recv_command, command);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t unit_idx = msg[4];
|
||||
uint8_t mantissa_range = msg[5];
|
||||
|
||||
if (mantissa_range > 4) {
|
||||
ESP_LOGE(TAG, "Received invalid message (mantissa size too large %d, expected 4)", mantissa_range);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate exponent
|
||||
float exponent = msg[6] & 0x3F;
|
||||
if (msg[6] & 0x40) {
|
||||
exponent = -exponent;
|
||||
}
|
||||
exponent = powf(10, exponent);
|
||||
if (msg[6] & 0x80) {
|
||||
exponent = -exponent;
|
||||
}
|
||||
|
||||
// Calculate mantissa
|
||||
uint32_t mantissa = 0;
|
||||
for (int i = 0; i < mantissa_range; i++) {
|
||||
mantissa <<= 8;
|
||||
mantissa |= msg[i + 7];
|
||||
}
|
||||
|
||||
// Calculate the actual value
|
||||
float value = mantissa * exponent;
|
||||
|
||||
// Set sensor value
|
||||
this->set_sensor_value_(command, value, unit_idx);
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint8_t unit_idx) {
|
||||
const char *unit = UNITS[unit_idx];
|
||||
|
||||
// Standard sensors
|
||||
if (command == CMD_HEAT_ENERGY && this->heat_energy_sensor_ != nullptr) {
|
||||
this->heat_energy_sensor_->publish_state(value);
|
||||
} else if (command == CMD_POWER && this->power_sensor_ != nullptr) {
|
||||
this->power_sensor_->publish_state(value);
|
||||
} else if (command == CMD_TEMP1 && this->temp1_sensor_ != nullptr) {
|
||||
this->temp1_sensor_->publish_state(value);
|
||||
} else if (command == CMD_TEMP2 && this->temp2_sensor_ != nullptr) {
|
||||
this->temp2_sensor_->publish_state(value);
|
||||
} else if (command == CMD_TEMP_DIFF && this->temp_diff_sensor_ != nullptr) {
|
||||
this->temp_diff_sensor_->publish_state(value);
|
||||
} else if (command == CMD_FLOW && this->flow_sensor_ != nullptr) {
|
||||
this->flow_sensor_->publish_state(value);
|
||||
} else if (command == CMD_VOLUME && this->volume_sensor_ != nullptr) {
|
||||
this->volume_sensor_->publish_state(value);
|
||||
}
|
||||
|
||||
// Custom sensors
|
||||
for (int i = 0; i < this->custom_commands_.size(); i++) {
|
||||
if (command == this->custom_commands_[i]) {
|
||||
this->custom_sensors_[i]->publish_state(value);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value, unit);
|
||||
}
|
||||
|
||||
uint16_t crc16_ccitt(const uint8_t *buffer, int len) {
|
||||
uint32_t poly = 0x1021;
|
||||
uint32_t reg = 0x00;
|
||||
for (int i = 0; i < len; i++) {
|
||||
int mask = 0x80;
|
||||
while (mask > 0) {
|
||||
reg <<= 1;
|
||||
if (buffer[i] & mask) {
|
||||
reg |= 1;
|
||||
}
|
||||
mask >>= 1;
|
||||
if (reg & 0x10000) {
|
||||
reg &= 0xffff;
|
||||
reg ^= poly;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (uint16_t) reg;
|
||||
}
|
||||
|
||||
} // namespace kamstrup_kmp
|
||||
} // namespace esphome
|
131
esphome/components/kamstrup_kmp/kamstrup_kmp.h
Normal file
131
esphome/components/kamstrup_kmp/kamstrup_kmp.h
Normal file
|
@ -0,0 +1,131 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace kamstrup_kmp {
|
||||
|
||||
/*
|
||||
===========================================================================
|
||||
=== KAMSTRUP KMP ===
|
||||
===========================================================================
|
||||
|
||||
Kamstrup Meter Protocol (KMP) is a protocol used with Kamstrup district
|
||||
heating meters, e.g. Kamstrup MULTICAL 403.
|
||||
These devices register consumed heat from a district heating system.
|
||||
It does this by measuring the incoming and outgoing water temperature
|
||||
and by measuring the water flow. The temperature difference (delta T)
|
||||
together with the water flow results in consumed energy, typically
|
||||
in giga joule (GJ).
|
||||
|
||||
The Kamstrup Multical has an optical interface just above the display.
|
||||
This interface is essentially an RS-232 interface using a proprietary
|
||||
protocol (Kamstrup Meter Protocol [KMP]).
|
||||
|
||||
The integration uses this optical interface to periodically read the
|
||||
configured values (sensors) from the meter. Supported sensors are:
|
||||
- Heat Energy [GJ]
|
||||
- Current Power Consumption [kW]
|
||||
- Temperature 1 [°C]
|
||||
- Temperature 2 [°C]
|
||||
- Temperature Difference [°K]
|
||||
- Water Flow [l/h]
|
||||
- Volume [m3]
|
||||
|
||||
Apart from these supported 'fixed' sensors, the user can configure up to
|
||||
five custom sensors. The KMP command (16 bit unsigned int) has to be
|
||||
provided in that case.
|
||||
|
||||
Note:
|
||||
The optical interface is enabled as soon as a button on the meter is pushed.
|
||||
The interface stays active for a few minutes. To keep the interface 'alive'
|
||||
magnets must be placed around the optical sensor.
|
||||
|
||||
Units:
|
||||
Units are set using the regular Sensor config in the user yaml. However,
|
||||
KMP does also send the correct unit with every value. When DEBUG logging
|
||||
is enabled, the received value with the received unit are logged.
|
||||
|
||||
Acknowledgement:
|
||||
This interface was inspired by:
|
||||
- https://atomstar.tweakblogs.net/blog/19110/reading-out-kamstrup-multical-402-403-with-home-built-optical-head
|
||||
- https://wiki.hal9k.dk/projects/kamstrup
|
||||
*/
|
||||
|
||||
// KMP Commands
|
||||
static const uint16_t CMD_HEAT_ENERGY = 0x003C;
|
||||
static const uint16_t CMD_POWER = 0x0050;
|
||||
static const uint16_t CMD_TEMP1 = 0x0056;
|
||||
static const uint16_t CMD_TEMP2 = 0x0057;
|
||||
static const uint16_t CMD_TEMP_DIFF = 0x0059;
|
||||
static const uint16_t CMD_FLOW = 0x004A;
|
||||
static const uint16_t CMD_VOLUME = 0x0044;
|
||||
|
||||
// KMP units
|
||||
static const char *const UNITS[] = {
|
||||
"", "Wh", "kWh", "MWh", "GWh", "J", "kJ", "MJ", "GJ", "Cal",
|
||||
"kCal", "Mcal", "Gcal", "varh", "kvarh", "Mvarh", "Gvarh", "VAh", "kVAh", "MVAh",
|
||||
"GVAh", "kW", "kW", "MW", "GW", "kvar", "kvar", "Mvar", "Gvar", "VA",
|
||||
"kVA", "MVA", "GVA", "V", "A", "kV", "kA", "C", "K", "l",
|
||||
"m3", "l/h", "m3/h", "m3xC", "ton", "ton/h", "h", "hh:mm:ss", "yy:mm:dd", "yyyy:mm:dd",
|
||||
"mm:dd", "", "bar", "RTC", "ASCII", "m3 x 10", "ton x 10", "GJ x 10", "minutes", "Bitfield",
|
||||
"s", "ms", "days", "RTC-Q", "Datetime"};
|
||||
|
||||
class KamstrupKMPComponent : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
void set_heat_energy_sensor(sensor::Sensor *sensor) { this->heat_energy_sensor_ = sensor; }
|
||||
void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; }
|
||||
void set_temp1_sensor(sensor::Sensor *sensor) { this->temp1_sensor_ = sensor; }
|
||||
void set_temp2_sensor(sensor::Sensor *sensor) { this->temp2_sensor_ = sensor; }
|
||||
void set_temp_diff_sensor(sensor::Sensor *sensor) { this->temp_diff_sensor_ = sensor; }
|
||||
void set_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; }
|
||||
void set_volume_sensor(sensor::Sensor *sensor) { this->volume_sensor_ = sensor; }
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
void loop() override;
|
||||
void add_custom_sensor(sensor::Sensor *sensor, uint16_t command) {
|
||||
this->custom_sensors_.push_back(sensor);
|
||||
this->custom_commands_.push_back(command);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Sensors
|
||||
sensor::Sensor *heat_energy_sensor_{nullptr};
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
sensor::Sensor *temp1_sensor_{nullptr};
|
||||
sensor::Sensor *temp2_sensor_{nullptr};
|
||||
sensor::Sensor *temp_diff_sensor_{nullptr};
|
||||
sensor::Sensor *flow_sensor_{nullptr};
|
||||
sensor::Sensor *volume_sensor_{nullptr};
|
||||
|
||||
// Custom sensors and commands
|
||||
std::vector<sensor::Sensor *> custom_sensors_;
|
||||
std::vector<uint16_t> custom_commands_;
|
||||
|
||||
// Command queue
|
||||
std::queue<uint16_t> command_queue_;
|
||||
|
||||
// Methods
|
||||
|
||||
// Sends a command to the meter and receives its response
|
||||
void send_command_(uint16_t command);
|
||||
// Sends a message to the meter. A prefix/suffix and CRC are added
|
||||
void send_message_(const uint8_t *msg, int msg_len);
|
||||
// Clears and data that might be in the UART Rx buffer
|
||||
void clear_uart_rx_buffer_();
|
||||
// Reads and validates the response to a send command
|
||||
void read_command_(uint16_t command);
|
||||
// Parses a received message
|
||||
void parse_command_message_(uint16_t command, const uint8_t *msg, int msg_len);
|
||||
// Sets the received value to the correct sensor
|
||||
void set_sensor_value_(uint16_t command, float value, uint8_t unit_idx);
|
||||
};
|
||||
|
||||
// "true" CCITT CRC-16
|
||||
uint16_t crc16_ccitt(const uint8_t *buffer, int len);
|
||||
|
||||
} // namespace kamstrup_kmp
|
||||
} // namespace esphome
|
132
esphome/components/kamstrup_kmp/sensor.py
Normal file
132
esphome/components/kamstrup_kmp/sensor.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.const import (
|
||||
CONF_COMMAND,
|
||||
CONF_CUSTOM,
|
||||
CONF_FLOW,
|
||||
CONF_ID,
|
||||
CONF_POWER,
|
||||
CONF_VOLUME,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLUME,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_CUBIC_METER,
|
||||
UNIT_EMPTY,
|
||||
UNIT_KELVIN,
|
||||
UNIT_KILOWATT,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@cfeenstra1024"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
kamstrup_kmp_ns = cg.esphome_ns.namespace("kamstrup_kmp")
|
||||
KamstrupKMPComponent = kamstrup_kmp_ns.class_(
|
||||
"KamstrupKMPComponent", cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
|
||||
CONF_HEAT_ENERGY = "heat_energy"
|
||||
CONF_TEMP1 = "temp1"
|
||||
CONF_TEMP2 = "temp2"
|
||||
CONF_TEMP_DIFF = "temp_diff"
|
||||
|
||||
UNIT_GIGA_JOULE = "GJ"
|
||||
UNIT_LITRE_PER_HOUR = "l/h"
|
||||
|
||||
# Note: The sensor units are set automatically based un the received data from the meter
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(KamstrupKMPComponent),
|
||||
cv.Optional(CONF_HEAT_ENERGY): sensor.sensor_schema(
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
unit_of_measurement=UNIT_GIGA_JOULE,
|
||||
),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_KILOWATT,
|
||||
),
|
||||
cv.Optional(CONF_TEMP1): sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
),
|
||||
cv.Optional(CONF_TEMP2): sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
),
|
||||
cv.Optional(CONF_TEMP_DIFF): sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_KELVIN,
|
||||
),
|
||||
cv.Optional(CONF_FLOW): sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLUME,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_LITRE_PER_HOUR,
|
||||
),
|
||||
cv.Optional(CONF_VOLUME): sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLUME,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
unit_of_measurement=UNIT_CUBIC_METER,
|
||||
),
|
||||
cv.Optional(CONF_CUSTOM): cv.ensure_list(
|
||||
sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
).extend({cv.Required(CONF_COMMAND): cv.hex_uint16_t})
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"kamstrup_kmp", baud_rate=1200, require_rx=True, require_tx=True
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
# Standard sensors
|
||||
for key in [
|
||||
CONF_HEAT_ENERGY,
|
||||
CONF_POWER,
|
||||
CONF_TEMP1,
|
||||
CONF_TEMP2,
|
||||
CONF_TEMP_DIFF,
|
||||
CONF_FLOW,
|
||||
CONF_VOLUME,
|
||||
]:
|
||||
if key not in config:
|
||||
continue
|
||||
conf = config[key]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, f"set_{key}_sensor")(sens))
|
||||
|
||||
# Custom sensors
|
||||
if CONF_CUSTOM in config:
|
||||
for conf in config[CONF_CUSTOM]:
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.add_custom_sensor(sens, conf[CONF_COMMAND]))
|
|
@ -40,9 +40,9 @@ There are three documented parameters for modes:
|
|||
00 04 = Energy output mode
|
||||
This mode outputs detailed signal energy values for each gate and the target distance.
|
||||
The data format consist of the following.
|
||||
Header HH, Length LL, Persence PP, Distance DD, Range Gate GG, 16 Gate Energies EE, Footer FF
|
||||
HH HH HH HH LL LL PP DD DD GG GG EE EE .. 16x .. FF FF FF FF
|
||||
F4 F3 F2 F1 00 23 00 00 00 00 01 00 00 .. .. .. .. F8 F7 F6 F5
|
||||
Header HH, Length LL, Persence PP, Distance DD, 16 Gate Energies EE, Footer FF
|
||||
HH HH HH HH LL LL PP DD DD EE EE .. 16x .. FF FF FF FF
|
||||
F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5
|
||||
00 00 = debug output mode
|
||||
This mode outputs detailed values consisting of 20 Dopplers, 16 Ranges for a total 20 * 16 * 4 bytes
|
||||
The data format consist of the following.
|
||||
|
@ -211,10 +211,11 @@ void LD2420Component::factory_reset_action() {
|
|||
void LD2420Component::restart_module_action() {
|
||||
ESP_LOGCONFIG(TAG, "Restarting LD2420 module...");
|
||||
this->send_module_restart();
|
||||
delay_microseconds_safe(45000);
|
||||
this->set_timeout(250, [this]() {
|
||||
this->set_config_mode(true);
|
||||
this->set_system_mode(system_mode_);
|
||||
this->set_config_mode(false);
|
||||
});
|
||||
ESP_LOGCONFIG(TAG, "LD2420 Restarted.");
|
||||
}
|
||||
|
||||
|
@ -527,10 +528,8 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
|||
this->write_byte(cmd_buffer[index]);
|
||||
}
|
||||
|
||||
delay_microseconds_safe(500); // give the module a moment to process it
|
||||
error = 0;
|
||||
if (frame.command == CMD_RESTART) {
|
||||
delay_microseconds_safe(25000); // Wait for the restart
|
||||
return 0; // restart does not reply exit now
|
||||
}
|
||||
|
||||
|
@ -538,7 +537,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
|||
while (available()) {
|
||||
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
|
||||
}
|
||||
delay_microseconds_safe(250);
|
||||
delay_microseconds_safe(1450);
|
||||
if (loop_count <= 0) {
|
||||
error = LD2420_ERROR_TIMEOUT;
|
||||
retry--;
|
||||
|
|
|
@ -96,6 +96,12 @@ esp_err_t configure_timer_frequency(ledc_mode_t speed_mode, ledc_timer_t timer_n
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
constexpr int ledc_angle_to_htop(float angle, uint8_t bit_depth) {
|
||||
return static_cast<int>(angle * ((1U << bit_depth) - 1) / 360.);
|
||||
}
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
void LEDCOutput::write_state(float state) {
|
||||
if (!initialized_) {
|
||||
ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!");
|
||||
|
@ -117,7 +123,8 @@ void LEDCOutput::write_state(float state) {
|
|||
#ifdef USE_ESP_IDF
|
||||
auto speed_mode = get_speed_mode(channel_);
|
||||
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
|
||||
ledc_set_duty(speed_mode, chan_num, duty);
|
||||
int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_);
|
||||
ledc_set_duty_with_hpoint(speed_mode, chan_num, duty, hpoint);
|
||||
ledc_update_duty(speed_mode, chan_num);
|
||||
#endif
|
||||
}
|
||||
|
@ -143,8 +150,10 @@ void LEDCOutput::setup() {
|
|||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_);
|
||||
|
||||
ESP_LOGV(TAG, "Configured frequency %f with a bit depth of %u bits", this->frequency_, this->bit_depth_);
|
||||
ESP_LOGV(TAG, "Angle of %.1f° results in hpoint %u", this->phase_angle_, hpoint);
|
||||
|
||||
ledc_channel_config_t chan_conf{};
|
||||
chan_conf.gpio_num = pin_->get_pin();
|
||||
|
@ -153,7 +162,7 @@ void LEDCOutput::setup() {
|
|||
chan_conf.intr_type = LEDC_INTR_DISABLE;
|
||||
chan_conf.timer_sel = timer_num;
|
||||
chan_conf.duty = inverted_ == pin_->is_inverted() ? 0 : (1U << bit_depth_);
|
||||
chan_conf.hpoint = 0;
|
||||
chan_conf.hpoint = hpoint;
|
||||
ledc_channel_config(&chan_conf);
|
||||
initialized_ = true;
|
||||
this->status_clear_error();
|
||||
|
@ -165,6 +174,7 @@ void LEDCOutput::dump_config() {
|
|||
LOG_PIN(" Pin ", this->pin_);
|
||||
ESP_LOGCONFIG(TAG, " LEDC Channel: %u", this->channel_);
|
||||
ESP_LOGCONFIG(TAG, " PWM Frequency: %.1f Hz", this->frequency_);
|
||||
ESP_LOGCONFIG(TAG, " Phase angle: %.1f°", this->phase_angle_);
|
||||
ESP_LOGCONFIG(TAG, " Bit depth: %u", this->bit_depth_);
|
||||
ESP_LOGV(TAG, " Max frequency for bit depth: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_));
|
||||
ESP_LOGV(TAG, " Min frequency for bit depth: %f",
|
||||
|
|
|
@ -19,6 +19,7 @@ class LEDCOutput : public output::FloatOutput, public Component {
|
|||
|
||||
void set_channel(uint8_t channel) { this->channel_ = channel; }
|
||||
void set_frequency(float frequency) { this->frequency_ = frequency; }
|
||||
void set_phase_angle(float angle) { this->phase_angle_ = angle; }
|
||||
/// Dynamically change frequency at runtime
|
||||
void update_frequency(float frequency) override;
|
||||
|
||||
|
@ -35,6 +36,7 @@ class LEDCOutput : public output::FloatOutput, public Component {
|
|||
InternalGPIOPin *pin_;
|
||||
uint8_t channel_{};
|
||||
uint8_t bit_depth_{};
|
||||
float phase_angle_{0.0f};
|
||||
float frequency_{};
|
||||
float duty_{0.0f};
|
||||
bool initialized_ = false;
|
||||
|
|
|
@ -3,6 +3,7 @@ from esphome.components import output
|
|||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import (
|
||||
CONF_PHASE_ANGLE,
|
||||
CONF_CHANNEL,
|
||||
CONF_FREQUENCY,
|
||||
CONF_ID,
|
||||
|
@ -46,6 +47,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
|||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency,
|
||||
cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15),
|
||||
cv.Optional(CONF_PHASE_ANGLE): cv.All(
|
||||
cv.only_with_esp_idf, cv.angle, cv.float_range(min=0.0, max=360.0)
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
@ -58,6 +62,8 @@ async def to_code(config):
|
|||
if CONF_CHANNEL in config:
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
cg.add(var.set_frequency(config[CONF_FREQUENCY]))
|
||||
if CONF_PHASE_ANGLE in config:
|
||||
cg.add(var.set_phase_angle(config[CONF_PHASE_ANGLE]))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
|
|
@ -129,7 +129,7 @@ void Logger::pre_setup() {
|
|||
this->uart_num_ = UART_NUM_2;
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->uart_num_ = -1;
|
||||
break;
|
||||
|
|
|
@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
|||
import esphome.codegen as cg
|
||||
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID
|
||||
from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID, CONF_VOLUME
|
||||
from esphome.core import CORE
|
||||
from esphome.coroutine import coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
@ -43,7 +43,6 @@ VolumeSetAction = media_player_ns.class_(
|
|||
)
|
||||
|
||||
|
||||
CONF_VOLUME = "volume"
|
||||
CONF_ON_IDLE = "on_idle"
|
||||
CONF_ON_PLAY = "on_play"
|
||||
CONF_ON_PAUSE = "on_pause"
|
||||
|
|
|
@ -29,6 +29,14 @@ void MHZ19Component::setup() {
|
|||
}
|
||||
|
||||
void MHZ19Component::update() {
|
||||
uint32_t now_ms = millis();
|
||||
uint32_t warmup_ms = this->warmup_seconds_ * 1000;
|
||||
if (now_ms < warmup_ms) {
|
||||
ESP_LOGW(TAG, "MHZ19 warming up, %ds left", (warmup_ms - now_ms) / 1000);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t response[MHZ19_RESPONSE_LENGTH];
|
||||
if (!this->mhz19_write_command_(MHZ19_COMMAND_GET_PPM, response)) {
|
||||
ESP_LOGW(TAG, "Reading data from MHZ19 failed!");
|
||||
|
@ -101,6 +109,8 @@ void MHZ19Component::dump_config() {
|
|||
} else if (this->abc_boot_logic_ == MHZ19_ABC_DISABLED) {
|
||||
ESP_LOGCONFIG(TAG, " Automatic baseline calibration disabled on boot");
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Warmup seconds: %ds", this->warmup_seconds_);
|
||||
}
|
||||
|
||||
} // namespace mhz19
|
||||
|
|
|
@ -25,6 +25,7 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice {
|
|||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; }
|
||||
void set_abc_enabled(bool abc_enabled) { abc_boot_logic_ = abc_enabled ? MHZ19_ABC_ENABLED : MHZ19_ABC_DISABLED; }
|
||||
void set_warmup_seconds(uint32_t seconds) { warmup_seconds_ = seconds; }
|
||||
|
||||
protected:
|
||||
bool mhz19_write_command_(const uint8_t *command, uint8_t *response);
|
||||
|
@ -32,6 +33,7 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice {
|
|||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *co2_sensor_{nullptr};
|
||||
MHZ19ABCLogic abc_boot_logic_{MHZ19_ABC_NONE};
|
||||
uint32_t warmup_seconds_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class MHZ19CalibrateZeroAction : public Action<Ts...> {
|
||||
|
|
|
@ -18,6 +18,7 @@ from esphome.const import (
|
|||
DEPENDENCIES = ["uart"]
|
||||
|
||||
CONF_AUTOMATIC_BASELINE_CALIBRATION = "automatic_baseline_calibration"
|
||||
CONF_WARMUP_TIME = "warmup_time"
|
||||
|
||||
mhz19_ns = cg.esphome_ns.namespace("mhz19")
|
||||
MHZ19Component = mhz19_ns.class_("MHZ19Component", cg.PollingComponent, uart.UARTDevice)
|
||||
|
@ -45,6 +46,9 @@ CONFIG_SCHEMA = (
|
|||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_AUTOMATIC_BASELINE_CALIBRATION): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_WARMUP_TIME, default="75s"
|
||||
): cv.positive_time_period_seconds,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
|
@ -68,6 +72,8 @@ async def to_code(config):
|
|||
if CONF_AUTOMATIC_BASELINE_CALIBRATION in config:
|
||||
cg.add(var.set_abc_enabled(config[CONF_AUTOMATIC_BASELINE_CALIBRATION]))
|
||||
|
||||
cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME]))
|
||||
|
||||
|
||||
CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
|
|
|
@ -287,7 +287,7 @@ def _load_model_data(manifest_path: Path):
|
|||
except cv.Invalid as e:
|
||||
raise EsphomeError(f"Invalid manifest file: {e}") from e
|
||||
|
||||
model_path = urljoin(str(manifest_path), manifest[CONF_MODEL])
|
||||
model_path = manifest_path.parent / manifest[CONF_MODEL]
|
||||
|
||||
with open(model_path, "rb") as f:
|
||||
model = f.read()
|
||||
|
|
|
@ -93,11 +93,18 @@ int MicroWakeWord::read_microphone_() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
size_t bytes_written = this->ring_buffer_->write((void *) this->input_buffer_, bytes_read);
|
||||
if (bytes_written != bytes_read) {
|
||||
ESP_LOGW(TAG, "Failed to write some data to ring buffer (written=%d, expected=%d)", bytes_written, bytes_read);
|
||||
size_t bytes_free = this->ring_buffer_->free();
|
||||
|
||||
if (bytes_free < bytes_read) {
|
||||
ESP_LOGW(TAG,
|
||||
"Not enough free bytes in ring buffer to store incoming audio data (free bytes=%d, incoming bytes=%d). "
|
||||
"Resetting the ring buffer. Wake word detection accuracy will be reduced.",
|
||||
bytes_free, bytes_read);
|
||||
|
||||
this->ring_buffer_->reset();
|
||||
}
|
||||
return bytes_written;
|
||||
|
||||
return this->ring_buffer_->write((void *) this->input_buffer_, bytes_read);
|
||||
}
|
||||
|
||||
void MicroWakeWord::loop() {
|
||||
|
@ -206,12 +213,6 @@ bool MicroWakeWord::initialize_models() {
|
|||
return false;
|
||||
}
|
||||
|
||||
this->preprocessor_stride_buffer_ = audio_samples_allocator.allocate(HISTORY_SAMPLES_TO_KEEP);
|
||||
if (this->preprocessor_stride_buffer_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate the audio preprocessor's stride buffer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->preprocessor_model_ = tflite::GetModel(G_AUDIO_PREPROCESSOR_INT8_TFLITE);
|
||||
if (this->preprocessor_model_->version() != TFLITE_SCHEMA_VERSION) {
|
||||
ESP_LOGE(TAG, "Wake word's audio preprocessor model's schema is not supported");
|
||||
|
@ -225,7 +226,7 @@ bool MicroWakeWord::initialize_models() {
|
|||
}
|
||||
|
||||
static tflite::MicroMutableOpResolver<18> preprocessor_op_resolver;
|
||||
static tflite::MicroMutableOpResolver<14> streaming_op_resolver;
|
||||
static tflite::MicroMutableOpResolver<17> streaming_op_resolver;
|
||||
|
||||
if (!this->register_preprocessor_ops_(preprocessor_op_resolver))
|
||||
return false;
|
||||
|
@ -329,7 +330,6 @@ bool MicroWakeWord::detect_wake_word_() {
|
|||
}
|
||||
|
||||
// Perform inference
|
||||
uint32_t streaming_size = micros();
|
||||
float streaming_prob = this->perform_streaming_inference_();
|
||||
|
||||
// Add the most recent probability to the sliding window
|
||||
|
@ -357,6 +357,9 @@ bool MicroWakeWord::detect_wake_word_() {
|
|||
for (auto &prob : this->recent_streaming_probabilities_) {
|
||||
prob = 0;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Wake word sliding average probability is %.3f and most recent probability is %.3f",
|
||||
sliding_window_average, streaming_prob);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -371,23 +374,6 @@ void MicroWakeWord::set_sliding_window_average_size(size_t size) {
|
|||
bool MicroWakeWord::slice_available_() {
|
||||
size_t available = this->ring_buffer_->available();
|
||||
|
||||
size_t free = this->ring_buffer_->free();
|
||||
|
||||
if (free < NEW_SAMPLES_TO_GET * sizeof(int16_t)) {
|
||||
// If the ring buffer is within one audio slice of being full, then wake word detection will have issues.
|
||||
// If this is constantly occuring, then some possibilities why are
|
||||
// 1) there are too many other slow components configured
|
||||
// 2) the ESP32 isn't fast enough; e.g., an ESP32 is much slower than an ESP32-S3 at inferences.
|
||||
// 3) the model is too large
|
||||
// 4) the model uses operations that are not optimized
|
||||
ESP_LOGW(TAG,
|
||||
"Audio buffer is nearly full. Wake word detection may be less accurate and have slower reponse times. "
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
"microWakeWord is designed for the ESP32-S3. The current platform is too slow for this model."
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
return available > (NEW_SAMPLES_TO_GET * sizeof(int16_t));
|
||||
}
|
||||
|
||||
|
@ -396,13 +382,12 @@ bool MicroWakeWord::stride_audio_samples_(int16_t **audio_samples) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Copy 320 bytes (160 samples over 10 ms) into preprocessor_audio_buffer_ from history in
|
||||
// preprocessor_stride_buffer_
|
||||
memcpy((void *) (this->preprocessor_audio_buffer_), (void *) (this->preprocessor_stride_buffer_),
|
||||
// Copy the last 320 bytes (160 samples over 10 ms) from the audio buffer to the start of the audio buffer
|
||||
memcpy((void *) (this->preprocessor_audio_buffer_), (void *) (this->preprocessor_audio_buffer_ + NEW_SAMPLES_TO_GET),
|
||||
HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t));
|
||||
|
||||
// Copy 640 bytes (320 samples over 20 ms) from the ring buffer
|
||||
// The first 320 bytes (160 samples over 10 ms) will be from history
|
||||
// Copy 640 bytes (320 samples over 20 ms) from the ring buffer into the audio buffer offset 320 bytes (160 samples
|
||||
// over 10 ms)
|
||||
size_t bytes_read = this->ring_buffer_->read((void *) (this->preprocessor_audio_buffer_ + HISTORY_SAMPLES_TO_KEEP),
|
||||
NEW_SAMPLES_TO_GET * sizeof(int16_t), pdMS_TO_TICKS(200));
|
||||
|
||||
|
@ -415,11 +400,6 @@ bool MicroWakeWord::stride_audio_samples_(int16_t **audio_samples) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Copy the last 320 bytes (160 samples over 10 ms) from the audio buffer into history stride buffer for the next
|
||||
// iteration
|
||||
memcpy((void *) (this->preprocessor_stride_buffer_), (void *) (this->preprocessor_audio_buffer_ + NEW_SAMPLES_TO_GET),
|
||||
HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t));
|
||||
|
||||
*audio_samples = this->preprocessor_audio_buffer_;
|
||||
return true;
|
||||
}
|
||||
|
@ -480,7 +460,7 @@ bool MicroWakeWord::register_preprocessor_ops_(tflite::MicroMutableOpResolver<18
|
|||
return true;
|
||||
}
|
||||
|
||||
bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<14> &op_resolver) {
|
||||
bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver) {
|
||||
if (op_resolver.AddCallOnce() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddVarHandle() != kTfLiteOk)
|
||||
|
@ -509,6 +489,12 @@ bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<14> &
|
|||
return false;
|
||||
if (op_resolver.AddQuantize() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddDepthwiseConv2D() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddAveragePool2D() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddMaxPool2D() != kTfLiteOk)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -128,7 +128,6 @@ class MicroWakeWord : public Component {
|
|||
|
||||
// Stores audio fed into feature generator preprocessor
|
||||
int16_t *preprocessor_audio_buffer_;
|
||||
int16_t *preprocessor_stride_buffer_;
|
||||
|
||||
bool detected_{false};
|
||||
|
||||
|
@ -181,7 +180,7 @@ class MicroWakeWord : public Component {
|
|||
bool register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver);
|
||||
|
||||
/// @brief Returns true if successfully registered the streaming model's TensorFlow operations
|
||||
bool register_streaming_ops_(tflite::MicroMutableOpResolver<14> &op_resolver);
|
||||
bool register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver);
|
||||
};
|
||||
|
||||
template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<MicroWakeWord> {
|
||||
|
|
|
@ -9,9 +9,53 @@ AUTO_LOAD = ["climate_ir"]
|
|||
mitsubishi_ns = cg.esphome_ns.namespace("mitsubishi")
|
||||
MitsubishiClimate = mitsubishi_ns.class_("MitsubishiClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_SCHEMA.extend(
|
||||
CONF_SET_FAN_MODE = "set_fan_mode"
|
||||
SetFanMode = mitsubishi_ns.enum("SetFanMode")
|
||||
SETFANMODE = {
|
||||
"quiet_4levels": SetFanMode.MITSUBISHI_FAN_Q4L,
|
||||
# "5levels": SetFanMode.MITSUBISHI_FAN_5L,
|
||||
"4levels": SetFanMode.MITSUBISHI_FAN_4L,
|
||||
"3levels": SetFanMode.MITSUBISHI_FAN_3L,
|
||||
}
|
||||
|
||||
CONF_SUPPORTS_DRY = "supports_dry"
|
||||
CONF_SUPPORTS_FAN_ONLY = "supports_fan_only"
|
||||
|
||||
CONF_HORIZONTAL_DEFAULT = "horizontal_default"
|
||||
HorizontalDirections = mitsubishi_ns.enum("HorizontalDirections")
|
||||
HORIZONTAL_DIRECTIONS = {
|
||||
"left": HorizontalDirections.HORIZONTAL_DIRECTION_LEFT,
|
||||
"middle-left": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE_LEFT,
|
||||
"middle": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE,
|
||||
"middle-right": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE_RIGHT,
|
||||
"right": HorizontalDirections.HORIZONTAL_DIRECTION_RIGHT,
|
||||
"split": HorizontalDirections.HORIZONTAL_DIRECTION_SPLIT,
|
||||
}
|
||||
|
||||
CONF_VERTICAL_DEFAULT = "vertical_default"
|
||||
VerticalDirections = mitsubishi_ns.enum("VerticalDirections")
|
||||
VERTICAL_DIRECTIONS = {
|
||||
"auto": VerticalDirections.VERTICAL_DIRECTION_AUTO,
|
||||
"up": VerticalDirections.VERTICAL_DIRECTION_UP,
|
||||
"middle-up": VerticalDirections.VERTICAL_DIRECTION_MIDDLE_UP,
|
||||
"middle": VerticalDirections.VERTICAL_DIRECTION_MIDDLE,
|
||||
"middle-down": VerticalDirections.VERTICAL_DIRECTION_MIDDLE_DOWN,
|
||||
"down": VerticalDirections.VERTICAL_DIRECTION_DOWN,
|
||||
}
|
||||
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MitsubishiClimate),
|
||||
cv.Optional(CONF_SET_FAN_MODE, default="3levels"): cv.enum(SETFANMODE),
|
||||
cv.Optional(CONF_SUPPORTS_DRY, default=False): cv.boolean,
|
||||
cv.Optional(CONF_SUPPORTS_FAN_ONLY, default=False): cv.boolean,
|
||||
cv.Optional(CONF_HORIZONTAL_DEFAULT, default="middle"): cv.enum(
|
||||
HORIZONTAL_DIRECTIONS
|
||||
),
|
||||
cv.Optional(CONF_VERTICAL_DEFAULT, default="middle"): cv.enum(
|
||||
VERTICAL_DIRECTIONS
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -19,3 +63,9 @@ CONFIG_SCHEMA = climate_ir.CLIMATE_IR_SCHEMA.extend(
|
|||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await climate_ir.register_climate_ir(var, config)
|
||||
|
||||
cg.add(var.set_fan_mode(config[CONF_SET_FAN_MODE]))
|
||||
cg.add(var.set_supports_dry(config[CONF_SUPPORTS_DRY]))
|
||||
cg.add(var.set_supports_fan_only(config[CONF_SUPPORTS_FAN_ONLY]))
|
||||
cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT]))
|
||||
cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT]))
|
||||
|
|
|
@ -8,12 +8,31 @@ static const char *const TAG = "mitsubishi.climate";
|
|||
|
||||
const uint32_t MITSUBISHI_OFF = 0x00;
|
||||
|
||||
const uint8_t MITSUBISHI_COOL = 0x18;
|
||||
const uint8_t MITSUBISHI_DRY = 0x10;
|
||||
const uint8_t MITSUBISHI_AUTO = 0x20;
|
||||
const uint8_t MITSUBISHI_HEAT = 0x08;
|
||||
const uint8_t MITSUBISHI_MODE_AUTO = 0x20;
|
||||
const uint8_t MITSUBISHI_MODE_COOL = 0x18;
|
||||
const uint8_t MITSUBISHI_MODE_DRY = 0x10;
|
||||
const uint8_t MITSUBISHI_MODE_FAN_ONLY = 0x38;
|
||||
const uint8_t MITSUBISHI_MODE_HEAT = 0x08;
|
||||
|
||||
const uint8_t MITSUBISHI_MODE_A_HEAT = 0x00;
|
||||
const uint8_t MITSUBISHI_MODE_A_DRY = 0x02;
|
||||
const uint8_t MITSUBISHI_MODE_A_COOL = 0x06;
|
||||
const uint8_t MITSUBISHI_MODE_A_AUTO = 0x06;
|
||||
|
||||
const uint8_t MITSUBISHI_WIDE_VANE_SWING = 0xC0;
|
||||
|
||||
const uint8_t MITSUBISHI_FAN_AUTO = 0x00;
|
||||
|
||||
const uint8_t MITSUBISHI_VERTICAL_VANE_SWING = 0x38;
|
||||
|
||||
// const uint8_t MITSUBISHI_AUTO = 0X80;
|
||||
const uint8_t MITSUBISHI_OTHERWISE = 0X40;
|
||||
const uint8_t MITSUBISHI_POWERFUL = 0x08;
|
||||
|
||||
// Optional presets used to enable some model features
|
||||
const uint8_t MITSUBISHI_ECONOCOOL = 0x20;
|
||||
const uint8_t MITSUBISHI_NIGHTMODE = 0xC1;
|
||||
|
||||
// Pulse parameters in usec
|
||||
const uint16_t MITSUBISHI_BIT_MARK = 430;
|
||||
const uint16_t MITSUBISHI_ONE_SPACE = 1250;
|
||||
|
@ -22,19 +41,97 @@ const uint16_t MITSUBISHI_HEADER_MARK = 3500;
|
|||
const uint16_t MITSUBISHI_HEADER_SPACE = 1700;
|
||||
const uint16_t MITSUBISHI_MIN_GAP = 17500;
|
||||
|
||||
// Marker bytes
|
||||
const uint8_t MITSUBISHI_BYTE00 = 0X23;
|
||||
const uint8_t MITSUBISHI_BYTE01 = 0XCB;
|
||||
const uint8_t MITSUBISHI_BYTE02 = 0X26;
|
||||
const uint8_t MITSUBISHI_BYTE03 = 0X01;
|
||||
const uint8_t MITSUBISHI_BYTE04 = 0X00;
|
||||
const uint8_t MITSUBISHI_BYTE13 = 0X00;
|
||||
const uint8_t MITSUBISHI_BYTE16 = 0X00;
|
||||
|
||||
climate::ClimateTraits MitsubishiClimate::traits() {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_action(false);
|
||||
traits.set_visual_min_temperature(MITSUBISHI_TEMP_MIN);
|
||||
traits.set_visual_max_temperature(MITSUBISHI_TEMP_MAX);
|
||||
traits.set_visual_temperature_step(1.0f);
|
||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF});
|
||||
|
||||
if (this->supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
if (this->supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
|
||||
if (this->supports_cool_ && this->supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
|
||||
|
||||
if (this->supports_dry_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
||||
if (this->supports_fan_only_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
||||
|
||||
// Default to only 3 levels in ESPHome even if most unit supports 4. The 3rd level is not used.
|
||||
traits.set_supported_fan_modes(
|
||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH});
|
||||
if (this->fan_mode_ == MITSUBISHI_FAN_Q4L)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_QUIET);
|
||||
if (/*this->fan_mode_ == MITSUBISHI_FAN_5L ||*/ this->fan_mode_ >= MITSUBISHI_FAN_4L)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE); // Shouldn't be used for this but it helps
|
||||
|
||||
traits.set_supported_swing_modes({climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH,
|
||||
climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL});
|
||||
|
||||
traits.set_supported_presets({climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO,
|
||||
climate::CLIMATE_PRESET_BOOST, climate::CLIMATE_PRESET_SLEEP});
|
||||
|
||||
return traits;
|
||||
}
|
||||
|
||||
void MitsubishiClimate::transmit_state() {
|
||||
uint32_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x30,
|
||||
0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
// Byte 0-4: Constant: 0x23, 0xCB, 0x26, 0x01, 0x00
|
||||
// Byte 5: On=0x20, Off: 0x00
|
||||
// Byte 6: MODE (See MODEs above (Heat/Dry/Cool/Auto/FanOnly)
|
||||
// Byte 7: TEMP bits 0,1,2,3, added to MITSUBISHI_TEMP_MIN
|
||||
// Example: 0x00 = 0°C+MITSUBISHI_TEMP_MIN = 16°C; 0x07 = 7°C+MITSUBISHI_TEMP_MIN = 23°C
|
||||
// Byte 8: MODE_A & Wide Vane (if present)
|
||||
// MODE_A bits 0,1,2 different than Byte 6 (See MODE_As above)
|
||||
// Wide Vane bits 4,5,6,7 (Middle = 0x30)
|
||||
// Byte 9: FAN/Vertical Vane/Switch To Auto
|
||||
// FAN (Speed) bits 0,1,2
|
||||
// Vertical Vane bits 3,4,5 (Auto = 0x00)
|
||||
// Switch To Auto bits 6,7
|
||||
// Byte 10: CLOCK Current time as configured on remote (0x00=Not used)
|
||||
// Byte 11: END CLOCK Stop time of HVAC (0x00 for no setting)
|
||||
// Byte 12: START CLOCK Start time of HVAC (0x00 for no setting)
|
||||
// Byte 13: Constant 0x00
|
||||
// Byte 14: HVAC specfic, i.e. ECONO COOL, CLEAN MODE, always 0x00
|
||||
// Byte 15: HVAC specfic, i.e. POWERFUL, SMART SET, PLASMA, always 0x00
|
||||
// Byte 16: Constant 0x00
|
||||
// Byte 17: Checksum: SUM[Byte0...Byte16]
|
||||
uint32_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state[6] = MITSUBISHI_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state[6] = MITSUBISHI_HEAT;
|
||||
remote_state[6] = MITSUBISHI_MODE_HEAT;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
remote_state[6] = MITSUBISHI_MODE_DRY;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state[6] = MITSUBISHI_MODE_COOL;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
remote_state[6] = MITSUBISHI_AUTO;
|
||||
remote_state[6] = MITSUBISHI_MODE_AUTO;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
remote_state[6] = MITSUBISHI_MODE_FAN_ONLY;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
|
@ -42,17 +139,111 @@ void MitsubishiClimate::transmit_state() {
|
|||
break;
|
||||
}
|
||||
|
||||
remote_state[7] = (uint8_t) roundf(clamp<float>(this->target_temperature, MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) -
|
||||
MITSUBISHI_TEMP_MIN);
|
||||
// Temperature
|
||||
if (this->mode == climate::CLIMATE_MODE_DRY) {
|
||||
remote_state[7] = 24 - MITSUBISHI_TEMP_MIN; // Remote sends always 24°C if "Dry" mode is selected
|
||||
} else {
|
||||
remote_state[7] = (uint8_t) roundf(
|
||||
clamp<float>(this->target_temperature, MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) - MITSUBISHI_TEMP_MIN);
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Sending Mitsubishi target temp: %.1f state: %02" PRIX32 " mode: %02" PRIX32 " temp: %02" PRIX32,
|
||||
this->target_temperature, remote_state[5], remote_state[6], remote_state[7]);
|
||||
// Wide Vane
|
||||
switch (this->swing_mode) {
|
||||
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
remote_state[8] = remote_state[8] | MITSUBISHI_WIDE_VANE_SWING; // Wide Vane Swing
|
||||
break;
|
||||
case climate::CLIMATE_SWING_OFF:
|
||||
default:
|
||||
remote_state[8] = remote_state[8] | this->default_horizontal_direction_; // Off--> horizontal default position
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "default_horizontal_direction_: %02X", this->default_horizontal_direction_);
|
||||
|
||||
// Fan Speed & Vertical Vane
|
||||
// Map of Climate fan mode to this device expected value
|
||||
// For 3Level: Low = 1, Medium = 2, High = 3
|
||||
// For 4Level: Low = 1, Middle = 2, Medium = 3, High = 4
|
||||
// For 5Level: Low = 1, Middle = 2, Medium = 3, High = 4
|
||||
// For 4Level + Quiet: Low = 1, Middle = 2, Medium = 3, High = 4, Quiet = 5
|
||||
|
||||
switch (this->fan_mode.value()) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
remote_state[9] = 1;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
if (this->fan_mode_ == MITSUBISHI_FAN_3L) {
|
||||
remote_state[9] = 2;
|
||||
} else {
|
||||
remote_state[9] = 3;
|
||||
}
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
if (this->fan_mode_ == MITSUBISHI_FAN_3L) {
|
||||
remote_state[9] = 3;
|
||||
} else {
|
||||
remote_state[9] = 4;
|
||||
}
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MIDDLE:
|
||||
remote_state[9] = 2;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_QUIET:
|
||||
remote_state[9] = 5;
|
||||
break;
|
||||
default:
|
||||
remote_state[9] = MITSUBISHI_FAN_AUTO;
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "fan: %02x state: %02x", this->fan_mode.value(), remote_state[9]);
|
||||
|
||||
// Vertical Vane
|
||||
switch (this->swing_mode) {
|
||||
case climate::CLIMATE_SWING_VERTICAL:
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
remote_state[9] = remote_state[9] | MITSUBISHI_VERTICAL_VANE_SWING | MITSUBISHI_OTHERWISE; // Vane Swing
|
||||
break;
|
||||
case climate::CLIMATE_SWING_OFF:
|
||||
default:
|
||||
remote_state[9] = remote_state[9] | this->default_vertical_direction_ |
|
||||
MITSUBISHI_OTHERWISE; // Off--> vertical default position
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "default_vertical_direction_: %02X", this->default_vertical_direction_);
|
||||
|
||||
// Special modes
|
||||
switch (this->preset.value()) {
|
||||
case climate::CLIMATE_PRESET_ECO:
|
||||
remote_state[6] = MITSUBISHI_MODE_COOL | MITSUBISHI_OTHERWISE;
|
||||
remote_state[8] = (remote_state[8] & ~7) | MITSUBISHI_MODE_A_COOL;
|
||||
remote_state[14] = MITSUBISHI_ECONOCOOL;
|
||||
break;
|
||||
case climate::CLIMATE_PRESET_SLEEP:
|
||||
remote_state[9] = MITSUBISHI_FAN_AUTO;
|
||||
remote_state[14] = MITSUBISHI_NIGHTMODE;
|
||||
break;
|
||||
case climate::CLIMATE_PRESET_BOOST:
|
||||
remote_state[6] |= MITSUBISHI_OTHERWISE;
|
||||
remote_state[15] = MITSUBISHI_POWERFUL;
|
||||
break;
|
||||
case climate::CLIMATE_PRESET_NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Checksum
|
||||
for (int i = 0; i < 17; i++) {
|
||||
remote_state[17] += remote_state[i];
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "sending: %02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X",
|
||||
remote_state[0], remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5],
|
||||
remote_state[6], remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11],
|
||||
remote_state[12], remote_state[13], remote_state[14], remote_state[15], remote_state[16], remote_state[17]);
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto *data = transmit.get_data();
|
||||
|
||||
|
@ -81,5 +272,119 @@ void MitsubishiClimate::transmit_state() {
|
|||
transmit.perform();
|
||||
}
|
||||
|
||||
bool MitsubishiClimate::parse_state_frame_(const uint8_t frame[]) { return false; }
|
||||
|
||||
bool MitsubishiClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
uint8_t state_frame[18] = {};
|
||||
|
||||
if (!data.expect_item(MITSUBISHI_HEADER_MARK, MITSUBISHI_HEADER_SPACE)) {
|
||||
ESP_LOGV(TAG, "Header fail");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t pos = 0; pos < 18; pos++) {
|
||||
uint8_t byte = 0;
|
||||
for (int8_t bit = 0; bit < 8; bit++) {
|
||||
if (data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ONE_SPACE)) {
|
||||
byte |= 1 << bit;
|
||||
} else if (!data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ZERO_SPACE)) {
|
||||
ESP_LOGV(TAG, "Byte %d bit %d fail", pos, bit);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
state_frame[pos] = byte;
|
||||
|
||||
// Check Header && Footer
|
||||
if ((pos == 0 && byte != MITSUBISHI_BYTE00) || (pos == 1 && byte != MITSUBISHI_BYTE01) ||
|
||||
(pos == 2 && byte != MITSUBISHI_BYTE02) || (pos == 3 && byte != MITSUBISHI_BYTE03) ||
|
||||
(pos == 4 && byte != MITSUBISHI_BYTE04) || (pos == 13 && byte != MITSUBISHI_BYTE13) ||
|
||||
(pos == 16 && byte != MITSUBISHI_BYTE16)) {
|
||||
ESP_LOGV(TAG, "Bytes 0,1,2,3,4,13 or 16 fail - invalid value");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// On/Off and Mode
|
||||
if (state_frame[5] == MITSUBISHI_OFF) {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
} else {
|
||||
switch (state_frame[6]) {
|
||||
case MITSUBISHI_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case MITSUBISHI_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case MITSUBISHI_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case MITSUBISHI_MODE_FAN_ONLY:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
case MITSUBISHI_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Temp
|
||||
this->target_temperature = state_frame[7] + MITSUBISHI_TEMP_MIN;
|
||||
|
||||
// Fan
|
||||
uint8_t fan = state_frame[9] & 0x07; //(Bit 0,1,2 = Speed)
|
||||
// Map of Climate fan mode to this device expected value
|
||||
// For 3Level: Low = 1, Medium = 2, High = 3
|
||||
// For 4Level: Low = 1, Middle = 2, Medium = 3, High = 4
|
||||
// For 5Level: Low = 1, Middle = 2, Medium = 3, High = 4
|
||||
// For 4Level + Quiet: Low = 1, Middle = 2, Medium = 3, High = 4, Quiet = 5
|
||||
climate::ClimateFanMode modes_mapping[8] = {
|
||||
climate::CLIMATE_FAN_AUTO,
|
||||
climate::CLIMATE_FAN_LOW,
|
||||
this->fan_mode_ == MITSUBISHI_FAN_3L ? climate::CLIMATE_FAN_MEDIUM : climate::CLIMATE_FAN_MIDDLE,
|
||||
this->fan_mode_ == MITSUBISHI_FAN_3L ? climate::CLIMATE_FAN_HIGH : climate::CLIMATE_FAN_MEDIUM,
|
||||
climate::CLIMATE_FAN_HIGH,
|
||||
climate::CLIMATE_FAN_QUIET,
|
||||
climate::CLIMATE_FAN_AUTO,
|
||||
climate::CLIMATE_FAN_AUTO};
|
||||
this->fan_mode = modes_mapping[fan];
|
||||
|
||||
// Wide Vane
|
||||
uint8_t wide_vane = state_frame[8] & 0xF0; // Bits 4,5,6,7
|
||||
switch (wide_vane) {
|
||||
case MITSUBISHI_WIDE_VANE_SWING:
|
||||
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
|
||||
break;
|
||||
default:
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
break;
|
||||
}
|
||||
|
||||
// Vertical Vane
|
||||
uint8_t vertical_vane = state_frame[9] & 0x38; // Bits 3,4,5
|
||||
switch (vertical_vane) {
|
||||
case MITSUBISHI_VERTICAL_VANE_SWING:
|
||||
if (this->swing_mode == climate::CLIMATE_SWING_HORIZONTAL) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_BOTH;
|
||||
} else {
|
||||
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (state_frame[14]) {
|
||||
case MITSUBISHI_ECONOCOOL:
|
||||
this->preset = climate::CLIMATE_PRESET_ECO;
|
||||
break;
|
||||
case MITSUBISHI_NIGHTMODE:
|
||||
this->preset = climate::CLIMATE_PRESET_SLEEP;
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Receiving: %s", format_hex_pretty(state_frame, 18).c_str());
|
||||
|
||||
this->publish_state();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace mitsubishi
|
||||
} // namespace esphome
|
||||
|
|
|
@ -11,13 +11,72 @@ namespace mitsubishi {
|
|||
const uint8_t MITSUBISHI_TEMP_MIN = 16; // Celsius
|
||||
const uint8_t MITSUBISHI_TEMP_MAX = 31; // Celsius
|
||||
|
||||
// Fan mode
|
||||
enum SetFanMode {
|
||||
MITSUBISHI_FAN_3L = 0, // 3 levels + auto
|
||||
MITSUBISHI_FAN_4L, // 4 levels + auto
|
||||
MITSUBISHI_FAN_Q4L, // Quiet + 4 levels + auto
|
||||
// MITSUBISHI_FAN_5L, // 5 levels + auto
|
||||
};
|
||||
|
||||
// Enum to represent horizontal directios
|
||||
enum HorizontalDirection {
|
||||
HORIZONTAL_DIRECTION_LEFT = 0x10,
|
||||
HORIZONTAL_DIRECTION_MIDDLE_LEFT = 0x20,
|
||||
HORIZONTAL_DIRECTION_MIDDLE = 0x30,
|
||||
HORIZONTAL_DIRECTION_MIDDLE_RIGHT = 0x40,
|
||||
HORIZONTAL_DIRECTION_RIGHT = 0x50,
|
||||
HORIZONTAL_DIRECTION_SPLIT = 0x80,
|
||||
};
|
||||
|
||||
// Enum to represent vertical directions
|
||||
enum VerticalDirection {
|
||||
VERTICAL_DIRECTION_AUTO = 0x00,
|
||||
VERTICAL_DIRECTION_UP = 0x08,
|
||||
VERTICAL_DIRECTION_MIDDLE_UP = 0x10,
|
||||
VERTICAL_DIRECTION_MIDDLE = 0x18,
|
||||
VERTICAL_DIRECTION_MIDDLE_DOWN = 0x20,
|
||||
VERTICAL_DIRECTION_DOWN = 0x28,
|
||||
};
|
||||
|
||||
class MitsubishiClimate : public climate_ir::ClimateIR {
|
||||
public:
|
||||
MitsubishiClimate() : climate_ir::ClimateIR(MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) {}
|
||||
MitsubishiClimate()
|
||||
: climate_ir::ClimateIR(MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX, 1.0f, true, true,
|
||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MIDDLE,
|
||||
climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH, climate::CLIMATE_FAN_QUIET},
|
||||
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL,
|
||||
climate::CLIMATE_SWING_HORIZONTAL},
|
||||
{climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_BOOST,
|
||||
climate::CLIMATE_PRESET_SLEEP}) {}
|
||||
|
||||
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||
void set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; }
|
||||
void set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; }
|
||||
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
||||
|
||||
void set_fan_mode(SetFanMode fan_mode) { this->fan_mode_ = fan_mode; }
|
||||
|
||||
void set_horizontal_default(HorizontalDirection horizontal_direction) {
|
||||
this->default_horizontal_direction_ = horizontal_direction;
|
||||
}
|
||||
void set_vertical_default(VerticalDirection vertical_direction) {
|
||||
this->default_vertical_direction_ = vertical_direction;
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Transmit via IR the state of this climate controller.
|
||||
// Transmit via IR the state of this climate controller.
|
||||
void transmit_state() override;
|
||||
// Handle received IR Buffer
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||
bool parse_state_frame_(const uint8_t frame[]);
|
||||
|
||||
SetFanMode fan_mode_;
|
||||
|
||||
HorizontalDirection default_horizontal_direction_;
|
||||
VerticalDirection default_vertical_direction_;
|
||||
|
||||
climate::ClimateTraits traits() override;
|
||||
};
|
||||
|
||||
} // namespace mitsubishi
|
||||
|
|
|
@ -187,11 +187,7 @@ void MQTTClientComponent::start_dnslookup_() {
|
|||
default:
|
||||
case ERR_ARG: {
|
||||
// error
|
||||
#if defined(USE_ESP8266)
|
||||
ESP_LOGW(TAG, "Error resolving MQTT broker IP address: %ld", err);
|
||||
#else
|
||||
ESP_LOGW(TAG, "Error resolving MQTT broker IP address: %d", err);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@ from esphome.components.esp32 import add_idf_sdkconfig_option
|
|||
from esphome.const import (
|
||||
CONF_ENABLE_IPV6,
|
||||
CONF_MIN_IPV6_ADDR_COUNT,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
@ -16,15 +19,20 @@ IPAddress = network_ns.class_("IPAddress")
|
|||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ENABLE_IPV6, default=False): cv.boolean,
|
||||
cv.SplitDefault(CONF_ENABLE_IPV6): cv.All(
|
||||
cv.boolean, cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040])
|
||||
),
|
||||
cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
if CONF_ENABLE_IPV6 in config:
|
||||
cg.add_define("USE_NETWORK_IPV6", config[CONF_ENABLE_IPV6])
|
||||
cg.add_define("USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT])
|
||||
cg.add_define(
|
||||
"USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT]
|
||||
)
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6])
|
||||
add_idf_sdkconfig_option(
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <cstdio>
|
||||
#include <array>
|
||||
#include "esphome/core/macros.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#if defined(USE_ESP_IDF) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0)
|
||||
#include <lwip/ip_addr.h>
|
||||
|
@ -116,7 +117,7 @@ struct IPAddress {
|
|||
bool is_set() { return !ip_addr_isany(&ip_addr_); }
|
||||
bool is_ip4() { return IP_IS_V4(&ip_addr_); }
|
||||
bool is_ip6() { return IP_IS_V6(&ip_addr_); }
|
||||
std::string str() const { return ipaddr_ntoa(&ip_addr_); }
|
||||
std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
|
||||
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||
IPAddress &operator+=(uint8_t increase) {
|
||||
|
|
|
@ -82,16 +82,16 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
|||
/**
|
||||
* Set the picture of an image component.
|
||||
* @param component The component name.
|
||||
* @param value The picture name.
|
||||
* @param value The picture id.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_picture("pic", "4");
|
||||
* it.set_component_picture("pic", 4);
|
||||
* ```
|
||||
*
|
||||
* This will change the image of the component `pic` to the image with ID `4`.
|
||||
*/
|
||||
void set_component_picture(const char *component, const char *picture);
|
||||
void set_component_picture(const char *component, uint8_t picture_id);
|
||||
/**
|
||||
* Set the background color of a component.
|
||||
* @param component The component name.
|
||||
|
|
|
@ -197,8 +197,8 @@ void Nextion::disable_component_touch(const char *component) {
|
|||
this->add_no_result_to_queue_with_printf_("disable_component_touch", "tsw %s,0", component);
|
||||
}
|
||||
|
||||
void Nextion::set_component_picture(const char *component, const char *picture) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.val=%s", component, picture);
|
||||
void Nextion::set_component_picture(const char *component, uint8_t picture_id) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.pic=%d", component, picture_id);
|
||||
}
|
||||
|
||||
void Nextion::set_component_text(const char *component, const char *text) {
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pmsx003 {
|
||||
|
||||
// known command bytes
|
||||
#define PMS_CMD_AUTO_MANUAL 0xE1 // data=0: perform measurement manually, data=1: perform measurement automatically
|
||||
#define PMS_CMD_TRIG_MANUAL 0xE2 // trigger a manual measurement
|
||||
#define PMS_CMD_ON_STANDBY 0xE4 // data=0: go to standby mode, data=1: go to normal mode
|
||||
static const uint8_t PMS_CMD_AUTO_MANUAL =
|
||||
0xE1; // data=0: perform measurement manually, data=1: perform measurement automatically
|
||||
static const uint8_t PMS_CMD_TRIG_MANUAL = 0xE2; // trigger a manual measurement
|
||||
static const uint8_t PMS_CMD_ON_STANDBY = 0xE4; // data=0: go to standby mode, data=1: go to normal mode
|
||||
|
||||
static const uint16_t PMS_STABILISING_MS = 30000; // time taken for the sensor to become stable after power on
|
||||
|
||||
|
|
|
@ -72,20 +72,14 @@ void QMC5883LComponent::dump_config() {
|
|||
LOG_SENSOR(" ", "Y Axis", this->y_sensor_);
|
||||
LOG_SENSOR(" ", "Z Axis", this->z_sensor_);
|
||||
LOG_SENSOR(" ", "Heading", this->heading_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
}
|
||||
float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void QMC5883LComponent::update() {
|
||||
uint8_t status = false;
|
||||
if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG)
|
||||
this->read_byte(QMC5883L_REGISTER_STATUS, &status);
|
||||
|
||||
uint16_t raw_x, raw_y, raw_z;
|
||||
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x) ||
|
||||
!this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y) ||
|
||||
!this->read_byte_16_(QMC5883L_REGISTER_DATA_Z_LSB, &raw_z)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
float mg_per_bit;
|
||||
switch (this->range_) {
|
||||
case QMC5883L_RANGE_200_UT:
|
||||
|
@ -99,12 +93,49 @@ void QMC5883LComponent::update() {
|
|||
}
|
||||
|
||||
// in µT
|
||||
const float x = int16_t(raw_x) * mg_per_bit * 0.1f;
|
||||
const float y = int16_t(raw_y) * mg_per_bit * 0.1f;
|
||||
const float z = int16_t(raw_z) * mg_per_bit * 0.1f;
|
||||
float x = NAN, y = NAN, z = NAN;
|
||||
if (this->x_sensor_ != nullptr || this->heading_sensor_ != nullptr) {
|
||||
uint16_t raw_x;
|
||||
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
x = int16_t(raw_x) * mg_per_bit * 0.1f;
|
||||
}
|
||||
if (this->y_sensor_ != nullptr || this->heading_sensor_ != nullptr) {
|
||||
uint16_t raw_y;
|
||||
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
y = int16_t(raw_y) * mg_per_bit * 0.1f;
|
||||
}
|
||||
if (this->z_sensor_ != nullptr) {
|
||||
uint16_t raw_z;
|
||||
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_Z_LSB, &raw_z)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
z = int16_t(raw_z) * mg_per_bit * 0.1f;
|
||||
}
|
||||
|
||||
float heading = atan2f(0.0f - x, y) * 180.0f / M_PI;
|
||||
ESP_LOGD(TAG, "Got x=%0.02fµT y=%0.02fµT z=%0.02fµT heading=%0.01f° status=%u", x, y, z, heading, status);
|
||||
float heading = NAN;
|
||||
if (this->heading_sensor_ != nullptr) {
|
||||
heading = atan2f(0.0f - x, y) * 180.0f / M_PI;
|
||||
}
|
||||
|
||||
float temp = NAN;
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
uint16_t raw_temp;
|
||||
if (!this->read_byte_16_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
temp = int16_t(raw_temp) * 0.01f;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Got x=%0.02fµT y=%0.02fµT z=%0.02fµT heading=%0.01f° temperature=%0.01f°C status=%u", x, y, z, heading,
|
||||
temp, status);
|
||||
|
||||
if (this->x_sensor_ != nullptr)
|
||||
this->x_sensor_->publish_state(x);
|
||||
|
@ -114,6 +145,8 @@ void QMC5883LComponent::update() {
|
|||
this->z_sensor_->publish_state(z);
|
||||
if (this->heading_sensor_ != nullptr)
|
||||
this->heading_sensor_->publish_state(heading);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temp);
|
||||
}
|
||||
|
||||
bool QMC5883LComponent::read_byte_16_(uint8_t a_register, uint16_t *data) {
|
||||
|
|
|
@ -40,6 +40,7 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice {
|
|||
void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; }
|
||||
void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; }
|
||||
void set_heading_sensor(sensor::Sensor *heading_sensor) { heading_sensor_ = heading_sensor; }
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
|
||||
protected:
|
||||
QMC5883LDatarate datarate_{QMC5883L_DATARATE_10_HZ};
|
||||
|
@ -49,6 +50,7 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice {
|
|||
sensor::Sensor *y_sensor_{nullptr};
|
||||
sensor::Sensor *z_sensor_{nullptr};
|
||||
sensor::Sensor *heading_sensor_{nullptr};
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
COMMUNICATION_FAILED,
|
||||
|
|
|
@ -6,12 +6,15 @@ from esphome.const import (
|
|||
CONF_FIELD_STRENGTH_X,
|
||||
CONF_FIELD_STRENGTH_Y,
|
||||
CONF_FIELD_STRENGTH_Z,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_ID,
|
||||
CONF_OVERSAMPLING,
|
||||
CONF_RANGE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_MAGNET,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_MICROTESLA,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_DEGREES,
|
||||
ICON_SCREEN_ROTATION,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
|
@ -79,6 +82,12 @@ heading_schema = sensor.sensor_schema(
|
|||
icon=ICON_SCREEN_ROTATION,
|
||||
accuracy_decimals=1,
|
||||
)
|
||||
temperature_schema = sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
|
@ -95,6 +104,7 @@ CONFIG_SCHEMA = (
|
|||
cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema,
|
||||
cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema,
|
||||
cv.Optional(CONF_HEADING): heading_schema,
|
||||
cv.Optional(CONF_TEMPERATURE): temperature_schema,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
|
@ -131,3 +141,6 @@ async def to_code(config):
|
|||
if CONF_HEADING in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HEADING])
|
||||
cg.add(var.set_heading_sensor(sens))
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
|
|
@ -51,5 +51,17 @@ void QrCode::draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t QrCode::get_size() {
|
||||
if (this->needs_update_) {
|
||||
this->generate_qr_code();
|
||||
this->needs_update_ = false;
|
||||
}
|
||||
|
||||
uint8_t size = qrcodegen_getSize(this->qr_);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace qr_code
|
||||
} // namespace esphome
|
||||
|
|
|
@ -24,6 +24,8 @@ class QrCode : public Component {
|
|||
|
||||
void generate_qr_code();
|
||||
|
||||
uint8_t get_size();
|
||||
|
||||
protected:
|
||||
std::string value_;
|
||||
qrcodegen_Ecc ecc_;
|
||||
|
|
1
esphome/components/qspi_amoled/__init__.py
Normal file
1
esphome/components/qspi_amoled/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@clydebarrow"]
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue